Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,151 @@
<?php
/**
* @file
* Contains \Drupal\field\ConfigImporterFieldPurger.
*/
namespace Drupal\field;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Processes field purges before a configuration synchronization.
*/
class ConfigImporterFieldPurger {
/**
* Processes fields targeted for purge as part of a configuration sync.
*
* This takes care of deleting the field if necessary, and purging the data on
* the fly.
*
* @param array $context
* The batch context.
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The config importer.
*/
public static function process(array &$context, ConfigImporter $config_importer) {
if (!isset($context['sandbox']['field'])) {
static::initializeSandbox($context, $config_importer);
}
// Get the list of field storages to purge.
$field_storages = static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete'));
// Get the first field storage to process.
$field_storage = reset($field_storages);
if (!isset($context['sandbox']['field']['current_storage_id']) || $context['sandbox']['field']['current_storage_id'] != $field_storage->id()) {
$context['sandbox']['field']['current_storage_id'] = $field_storage->id();
// If the storage has not been deleted yet we need to do that. This is the
// case when the storage deletion is staged.
if (!$field_storage->isDeleted()) {
$field_storage->delete();
}
}
field_purge_batch($context['sandbox']['field']['purge_batch_size'], $field_storage->uuid());
$context['sandbox']['field']['current_progress']++;
$fields_to_delete_count = count(static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete')));
if ($fields_to_delete_count == 0) {
$context['finished'] = 1;
}
else {
$context['finished'] = $context['sandbox']['field']['current_progress'] / $context['sandbox']['field']['steps_to_delete'];
$context['message'] = \Drupal::translation()->translate('Purging field @field_label', array('@field_label' => $field_storage->label()));
}
}
/**
* Initializes the batch context sandbox for processing field deletions.
*
* This calculates the number of steps necessary to purge all the field data
* and saves data for later use.
*
* @param array $context
* The batch context.
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The config importer.
*/
protected static function initializeSandbox(array &$context, ConfigImporter $config_importer) {
$context['sandbox']['field']['purge_batch_size'] = \Drupal::config('field.settings')->get('purge_batch_size');
// Save the future list of installed extensions to limit the amount of times
// the configuration is read from disk.
$context['sandbox']['field']['extensions'] = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
$context['sandbox']['field']['steps_to_delete'] = 0;
$fields = static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete'));
foreach ($fields as $field) {
$row_count = \Drupal::entityManager()->getStorage($field->getTargetEntityTypeId())
->countFieldData($field);
if ($row_count > 0) {
// The number of steps to delete each field is determined by the
// purge_batch_size setting. For example if the field has 9 rows and the
// batch size is 10 then this will add 1 step to $number_of_steps.
$how_many_steps = ceil($row_count / $context['sandbox']['field']['purge_batch_size']);
$context['sandbox']['field']['steps_to_delete'] += $how_many_steps;
}
}
// Each field possibly needs one last field_purge_batch() call to remove the
// last field and the field storage itself.
$context['sandbox']['field']['steps_to_delete'] += count($fields);
$context['sandbox']['field']['current_progress'] = 0;
}
/**
* Gets the list of fields to purge before configuration synchronization.
*
* If, during a configuration synchronization, a field is being deleted and
* the module that provides the field type is being uninstalled then the field
* data must be purged before the module is uninstalled. Also, if deleted
* fields exist whose field types are provided by modules that are being
* uninstalled their data need to be purged too.
*
* @param array $extensions
* The list of extensions that will be enabled after the configuration
* synchronization has finished.
* @param array $deletes
* The configuration that will be deleted by the configuration
* synchronization.
*
* @return \Drupal\field\Entity\FieldStorageConfig[]
* An array of field storages that need purging before configuration can be
* synchronized.
*/
public static function getFieldStoragesToPurge(array $extensions, array $deletes) {
$providers = array_keys($extensions['module']);
$providers[] = 'core';
$storages_to_delete = array();
// Gather fields that will be deleted during configuration synchronization
// where the module that provides the field type is also being uninstalled.
$field_storage_ids = array();
foreach ($deletes as $config_name) {
$field_storage_config_prefix = \Drupal::entityManager()->getDefinition('field_storage_config')->getConfigPrefix();
if (strpos($config_name, $field_storage_config_prefix . '.') === 0) {
$field_storage_ids[] = ConfigEntityStorage::getIDFromConfigName($config_name, $field_storage_config_prefix);
}
}
if (!empty($field_storage_ids)) {
$field_storages = \Drupal::entityQuery('field_storage_config')
->condition('id', $field_storage_ids, 'IN')
->condition('module', $providers, 'NOT IN')
->execute();
if (!empty($field_storages)) {
$storages_to_delete = FieldStorageConfig::loadMultiple($field_storages);
}
}
// Gather deleted fields from modules that are being uninstalled.
/** @var \Drupal\field\FieldStorageConfigInterface[] $field_storages */
$field_storages = entity_load_multiple_by_properties('field_storage_config', array('deleted' => TRUE, 'include_deleted' => TRUE));
foreach ($field_storages as $field_storage) {
if (!in_array($field_storage->getTypeProvider(), $providers)) {
$storages_to_delete[$field_storage->id()] = $field_storage;
}
}
return $storages_to_delete;
}
}

View file

@ -0,0 +1,348 @@
<?php
/**
* @file
* Contains \Drupal\field\Entity\FieldConfig.
*/
namespace Drupal\field\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldConfigBase;
use Drupal\Core\Field\FieldException;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\field\FieldConfigInterface;
/**
* Defines the Field entity.
*
* @ConfigEntityType(
* id = "field_config",
* label = @Translation("Field"),
* handlers = {
* "access" = "Drupal\field\FieldConfigAccessControlHandler",
* "storage" = "Drupal\field\FieldConfigStorage"
* },
* config_prefix = "field",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "field_name",
* "entity_type",
* "bundle",
* "label",
* "description",
* "required",
* "translatable",
* "default_value",
* "default_value_callback",
* "settings",
* "field_type",
* }
* )
*/
class FieldConfig extends FieldConfigBase implements FieldConfigInterface {
/**
* Flag indicating whether the field is deleted.
*
* The delete() method marks the field as "deleted" and removes the
* corresponding entry from the config storage, but keeps its definition in
* the state storage while field data is purged by a separate
* garbage-collection process.
*
* Deleted fields stay out of the regular entity lifecycle (notably, their
* values are not populated in loaded entities, and are not saved back).
*
* @var bool
*/
protected $deleted = FALSE;
/**
* The associated FieldStorageConfig entity.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* Constructs a FieldConfig object.
*
* In most cases, Field entities are created via
* entity_create('field_config', $values), where $values is the same
* parameter as in this constructor.
*
* @param array $values
* An array of field properties, keyed by property name. The
* storage associated to the field can be specified either with:
* - field_storage: the FieldStorageConfigInterface object,
* or by referring to an existing field storage in the current configuration
* with:
* - field_name: The field name.
* - entity_type: The entity type.
* Additionally, a 'bundle' property is required to indicate the entity
* bundle to which the field is attached to. Other array elements will be
* used to set the corresponding properties on the class; see the class
* property documentation for details.
*
* @see entity_create()
*/
public function __construct(array $values, $entity_type = 'field_config') {
// Allow either an injected FieldStorageConfig object, or a field_name and
// entity_type.
if (isset($values['field_storage'])) {
if (!$values['field_storage'] instanceof FieldStorageConfigInterface) {
throw new FieldException('Attempt to create a configurable field for a non-configurable field storage.');
}
$field_storage = $values['field_storage'];
$values['field_name'] = $field_storage->getName();
$values['entity_type'] = $field_storage->getTargetEntityTypeId();
// The internal property is fieldStorage, not field_storage.
unset($values['field_storage']);
$values['fieldStorage'] = $field_storage;
}
else {
if (empty($values['field_name'])) {
throw new FieldException('Attempt to create a field without a field_name.');
}
if (empty($values['entity_type'])) {
throw new FieldException(SafeMarkup::format('Attempt to create a field @field_name without an entity_type.', array('@field_name' => $values['field_name'])));
}
}
// 'bundle' is required in either case.
if (empty($values['bundle'])) {
throw new FieldException(SafeMarkup::format('Attempt to create a field @field_name without a bundle.', array('@field_name' => $values['field_name'])));
}
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function postCreate(EntityStorageInterface $storage) {
parent::postCreate($storage);
// Validate that we have a valid storage for this field. This throws an
// exception if the storage is invalid.
$this->getFieldStorageDefinition();
// 'Label' defaults to the field name (mostly useful for fields created in
// tests).
if (empty($this->label)) {
$this->label = $this->getName();
}
}
/**
* Overrides \Drupal\Core\Entity\Entity::preSave().
*
* @throws \Drupal\Core\Field\FieldException
* If the field definition is invalid.
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures at the configuration storage level.
*/
public function preSave(EntityStorageInterface $storage) {
$entity_manager = \Drupal::entityManager();
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$storage_definition = $this->getFieldStorageDefinition();
// Filter out unknown settings and make sure all settings are present, so
// that a complete field definition is passed to the various hooks and
// written to config.
$default_settings = $field_type_manager->getDefaultFieldSettings($storage_definition->getType());
$this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;
if ($this->isNew()) {
// Notify the entity storage.
$entity_manager->onFieldDefinitionCreate($this);
}
else {
// Some updates are always disallowed.
if ($this->entity_type != $this->original->entity_type) {
throw new FieldException("Cannot change an existing field's entity_type.");
}
if ($this->bundle != $this->original->bundle && empty($this->bundleRenameAllowed)) {
throw new FieldException("Cannot change an existing field's bundle.");
}
if ($storage_definition->uuid() != $this->original->getFieldStorageDefinition()->uuid()) {
throw new FieldException("Cannot change an existing field's storage.");
}
// Notify the entity storage.
$entity_manager->onFieldDefinitionUpdate($this, $this->original);
}
parent::preSave($storage);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
// Mark the field_storage_config as a a dependency.
$this->addDependency('config', $this->getFieldStorageDefinition()->getConfigDependencyName());
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $fields) {
$state = \Drupal::state();
parent::preDelete($storage, $fields);
// Keep the field definitions in the state storage so we can use them
// later during field_purge_batch().
$deleted_fields = $state->get('field.field.deleted') ?: array();
foreach ($fields as $field) {
if (!$field->deleted) {
$config = $field->toArray();
$config['deleted'] = TRUE;
$config['field_storage_uuid'] = $field->getFieldStorageDefinition()->uuid();
$deleted_fields[$field->uuid()] = $config;
}
}
$state->set('field.field.deleted', $deleted_fields);
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $fields) {
// Clear the cache upfront, to refresh the results of getBundles().
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Notify the entity storage.
foreach ($fields as $field) {
if (!$field->deleted) {
\Drupal::entityManager()->onFieldDefinitionDelete($field);
}
}
// If this is part of a configuration synchronization then the following
// configuration updates are not necessary.
$entity = reset($fields);
if ($entity->isSyncing()) {
return;
}
// Delete the associated field storages if they are not used anymore and are
// not persistent.
$storages_to_delete = array();
foreach ($fields as $field) {
$storage_definition = $field->getFieldStorageDefinition();
if (!$field->deleted && !$field->isUninstalling() && $storage_definition->isDeletable()) {
// Key by field UUID to avoid deleting the same storage twice.
$storages_to_delete[$storage_definition->uuid()] = $storage_definition;
}
}
if ($storages_to_delete) {
\Drupal::entityManager()->getStorage('field_storage_config')->delete($storages_to_delete);
}
}
/**
* {@inheritdoc}
*/
protected function linkTemplates() {
$link_templates = parent::linkTemplates();
if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
$link_templates["{$this->entity_type}-field-edit-form"] = 'entity.field_config.' . $this->entity_type . '_field_edit_form';
$link_templates["{$this->entity_type}-storage-edit-form"] = 'entity.field_config.' . $this->entity_type . '_storage_edit_form';
$link_templates["{$this->entity_type}-field-delete-form"] = 'entity.field_config.' . $this->entity_type . '_field_delete_form';
if (isset($link_templates['config-translation-overview'])) {
$link_templates["config-translation-overview.{$this->entity_type}"] = "entity.field_config.config_translation_overview.{$this->entity_type}";
}
}
return $link_templates;
}
/**
* {@inheritdoc}
*/
protected function urlRouteParameters($rel) {
$parameters = parent::urlRouteParameters($rel);
$entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
$parameters[$entity_type->getBundleEntityType()] = $this->bundle;
return $parameters;
}
/**
* {@inheritdoc}
*/
public function isDeleted() {
return $this->deleted;
}
/**
* {@inheritdoc}
*/
public function getFieldStorageDefinition() {
if (!$this->fieldStorage) {
$fields = $this->entityManager()->getFieldStorageDefinitions($this->entity_type);
if (!isset($fields[$this->field_name])) {
throw new FieldException(SafeMarkup::format('Attempt to create a field @field_name that does not exist on entity type @entity_type.', array('@field_name' => $this->field_name, '@entity_type' => $this->entity_type))); }
if (!$fields[$this->field_name] instanceof FieldStorageConfigInterface) {
throw new FieldException(SafeMarkup::format('Attempt to create a configurable field of non-configurable field storage @field_name.', array('@field_name' => $this->field_name, '@entity_type' => $this->entity_type)));
}
$this->fieldStorage = $fields[$this->field_name];
}
return $this->fieldStorage;
}
/**
* {@inheritdoc}
*/
public function isDisplayConfigurable($context) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getDisplayOptions($display_context) {
// Hide configurable fields by default.
return array('type' => 'hidden');
}
/**
* {@inheritdoc}
*/
public function isReadOnly() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isComputed() {
return FALSE;
}
/**
* Loads a field config entity based on the entity type and field name.
*
* @param string $entity_type_id
* ID of the entity type.
* @param string $bundle
* Bundle name.
* @param string $field_name
* Name of the field.
*
* @return static
* The field config entity if one exists for the provided field
* name, otherwise NULL.
*/
public static function loadByName($entity_type_id, $bundle, $field_name) {
return \Drupal::entityManager()->getStorage('field_config')->load($entity_type_id . '.' . $bundle . '.' . $field_name);
}
}

View file

@ -0,0 +1,823 @@
<?php
/**
* @file
* Contains \Drupal\field\Entity\FieldStorageConfig.
*/
namespace Drupal\field\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\field\FieldStorageConfigInterface;
/**
* Defines the Field storage configuration entity.
*
* @ConfigEntityType(
* id = "field_storage_config",
* label = @Translation("Field storage"),
* handlers = {
* "storage" = "Drupal\field\FieldStorageConfigStorage"
* },
* config_prefix = "storage",
* entity_keys = {
* "id" = "id",
* "label" = "id"
* },
* config_export = {
* "id",
* "field_name",
* "entity_type",
* "type",
* "settings",
* "module",
* "locked",
* "cardinality",
* "translatable",
* "indexes",
* "persist_with_no_fields",
* }
* )
*/
class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigInterface {
/**
* The maximum length of the field name, in characters.
*
* For fields created through Field UI, this includes the 'field_' prefix.
*/
const NAME_MAX_LENGTH = 32;
/**
* The field ID.
*
* The ID consists of 2 parts: the entity type and the field name.
*
* Example: node.body, user.field_main_image.
*
* @var string
*/
protected $id;
/**
* The field name.
*
* This is the name of the property under which the field values are placed in
* an entity: $entity->{$field_name}. The maximum length is
* Field:NAME_MAX_LENGTH.
*
* Example: body, field_main_image.
*
* @var string
*/
protected $field_name;
/**
* The name of the entity type the field can be attached to.
*
* @var string
*/
protected $entity_type;
/**
* The field type.
*
* Example: text, integer.
*
* @var string
*/
protected $type;
/**
* The name of the module that provides the field type.
*
* @var string
*/
protected $module;
/**
* Field-type specific settings.
*
* An array of key/value pairs, The keys and default values are defined by the
* field type.
*
* @var array
*/
protected $settings = [];
/**
* The field cardinality.
*
* The maximum number of values the field can hold. Possible values are
* positive integers or
* FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED. Defaults to 1.
*
* @var int
*/
protected $cardinality = 1;
/**
* Flag indicating whether the field is translatable.
*
* Defaults to TRUE.
*
* @var bool
*/
protected $translatable = TRUE;
/**
* Flag indicating whether the field is available for editing.
*
* If TRUE, some actions not available though the UI (but are still possible
* through direct API manipulation):
* - field settings cannot be changed,
* - new fields cannot be created
* - existing fields cannot be deleted.
* Defaults to FALSE.
*
* @var bool
*/
protected $locked = FALSE;
/**
* Flag indicating whether the field storage should be deleted when orphaned.
*
* By default field storages for configurable fields are removed when there
* are no remaining fields using them. If multiple modules provide bundles
* which need to use the same field storage then setting this to TRUE will
* preserve the field storage regardless of what happens to the bundles. The
* classic use case for this is node body field storage since Book, Forum, the
* Standard profile and bundle (node type) creation through the UI all use
* same field storage.
*
* @var bool
*/
protected $persist_with_no_fields = FALSE;
/**
* The custom storage indexes for the field data storage.
*
* This set of indexes is merged with the "default" indexes specified by the
* field type in hook_field_schema() to determine the actual set of indexes
* that get created.
*
* The indexes are defined using the same definition format as Schema API
* index specifications. Only columns that are part of the field schema, as
* defined by the field type in hook_field_schema(), are allowed.
*
* Some storage backends might not support indexes, and discard that
* information.
*
* @var array
*/
protected $indexes = [];
/**
* Flag indicating whether the field is deleted.
*
* The delete() method marks the field as "deleted" and removes the
* corresponding entry from the config storage, but keeps its definition in
* the state storage while field data is purged by a separate
* garbage-collection process.
*
* Deleted fields stay out of the regular entity lifecycle (notably, their
* values are not populated in loaded entities, and are not saved back).
*
* @var bool
*/
protected $deleted = FALSE;
/**
* The field schema.
*
* @var array
*/
protected $schema;
/**
* An array of field property definitions.
*
* @var \Drupal\Core\TypedData\DataDefinitionInterface[]
*
* @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
*/
protected $propertyDefinitions;
/**
* Static flag set to prevent recursion during field deletes.
*
* @var bool
*/
protected static $inDeletion = FALSE;
/**
* Constructs a FieldStorageConfig object.
*
* @param array $values
* An array of field properties, keyed by property name. Most array
* elements will be used to set the corresponding properties on the class;
* see the class property documentation for details. Some array elements
* have special meanings and a few are required. Special elements are:
* - name: required. As a temporary Backwards Compatibility layer right now,
* a 'field_name' property can be accepted in place of 'id'.
* - entity_type: required.
* - type: required.
*
* In most cases, Field entities are created via
* entity_create('field_storage_config', $values)), where $values is the same
* parameter as in this constructor.
*
* @see entity_create()
*/
public function __construct(array $values, $entity_type = 'field_storage_config') {
// Check required properties.
if (empty($values['field_name'])) {
throw new FieldException('Attempt to create a field storage without a field name.');
}
if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['field_name'])) {
throw new FieldException(SafeMarkup::format('Attempt to create a field storage @field_name with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character', array('@field_name' => $values['field_name'])));
}
if (empty($values['type'])) {
throw new FieldException(SafeMarkup::format('Attempt to create a field storage @field_name with no type.', array('@field_name' => $values['field_name'])));
}
if (empty($values['entity_type'])) {
throw new FieldException(SafeMarkup::format('Attempt to create a field storage @field_name with no entity_type.', array('@field_name' => $values['field_name'])));
}
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->getTargetEntityTypeId() . '.' . $this->getName();
}
/**
* Overrides \Drupal\Core\Entity\Entity::preSave().
*
* @throws \Drupal\Core\Field\FieldException
* If the field definition is invalid.
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures at the configuration storage level.
*/
public function preSave(EntityStorageInterface $storage) {
// Clear the derived data about the field.
unset($this->schema);
// Filter out unknown settings and make sure all settings are present, so
// that a complete field definition is passed to the various hooks and
// written to config.
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$default_settings = $field_type_manager->getDefaultStorageSettings($this->type);
$this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;
if ($this->isNew()) {
$this->preSaveNew($storage);
}
else {
$this->preSaveUpdated($storage);
}
parent::preSave($storage);
}
/**
* Prepares saving a new field definition.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage.
*
* @throws \Drupal\Core\Field\FieldException If the field definition is invalid.
*/
protected function preSaveNew(EntityStorageInterface $storage) {
$entity_manager = \Drupal::entityManager();
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
// Assign the ID.
$this->id = $this->id();
// Field name cannot be longer than FieldStorageConfig::NAME_MAX_LENGTH characters.
// We use Unicode::strlen() because the DB layer assumes that column widths
// are given in characters rather than bytes.
if (Unicode::strlen($this->getName()) > static::NAME_MAX_LENGTH) {
throw new FieldException(SafeMarkup::format(
'Attempt to create a field storage with an name longer than @max characters: %name', array(
'@max' => static::NAME_MAX_LENGTH,
'%name' => $this->getName(),
)
));
}
// Disallow reserved field names.
$disallowed_field_names = array_keys($entity_manager->getBaseFieldDefinitions($this->getTargetEntityTypeId()));
if (in_array($this->getName(), $disallowed_field_names)) {
throw new FieldException(SafeMarkup::format('Attempt to create field storage %name which is reserved by entity type %type.', array('%name' => $this->getName(), '%type' => $this->getTargetEntityTypeId())));
}
// Check that the field type is known.
$field_type = $field_type_manager->getDefinition($this->getType(), FALSE);
if (!$field_type) {
throw new FieldException(SafeMarkup::format('Attempt to create a field storage of unknown type %type.', array('%type' => $this->getType())));
}
$this->module = $field_type['provider'];
// Notify the entity manager.
$entity_manager->onFieldStorageDefinitionCreate($this);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
// Ensure the field is dependent on the providing module.
$this->addDependency('module', $this->getTypeProvider());
// Ensure the field is dependent on the provider of the entity type.
$entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
$this->addDependency('module', $entity_type->getProvider());
return $this->dependencies;
}
/**
* Prepares saving an updated field definition.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage.
*/
protected function preSaveUpdated(EntityStorageInterface $storage) {
$module_handler = \Drupal::moduleHandler();
$entity_manager = \Drupal::entityManager();
// Some updates are always disallowed.
if ($this->getType() != $this->original->getType()) {
throw new FieldException("Cannot change the field type for an existing field storage.");
}
if ($this->getTargetEntityTypeId() != $this->original->getTargetEntityTypeId()) {
throw new FieldException("Cannot change the entity type for an existing field storage.");
}
// See if any module forbids the update by throwing an exception. This
// invokes hook_field_storage_config_update_forbid().
$module_handler->invokeAll('field_storage_config_update_forbid', array($this, $this->original));
// Notify the entity manager. A listener can reject the definition
// update as invalid by raising an exception, which stops execution before
// the definition is written to config.
$entity_manager->onFieldStorageDefinitionUpdate($this, $this->original);
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
if ($update) {
// Invalidate the render cache for all affected entities.
$entity_manager = \Drupal::entityManager();
$entity_type = $this->getTargetEntityTypeId();
if ($entity_manager->hasHandler($entity_type, 'view_builder')) {
$entity_manager->getViewBuilder($entity_type)->resetCache();
}
}
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $field_storages) {
$state = \Drupal::state();
// Set the static flag so that we don't delete field storages whilst
// deleting fields.
static::$inDeletion = TRUE;
// Delete or fix any configuration that is dependent, for example, fields.
parent::preDelete($storage, $field_storages);
// Keep the field definitions in the state storage so we can use them later
// during field_purge_batch().
$deleted_storages = $state->get('field.storage.deleted') ?: array();
foreach ($field_storages as $field_storage) {
if (!$field_storage->deleted) {
$config = $field_storage->toArray();
$config['deleted'] = TRUE;
$config['bundles'] = $field_storage->getBundles();
$deleted_storages[$field_storage->uuid()] = $config;
}
}
$state->set('field.storage.deleted', $deleted_storages);
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $fields) {
// Notify the storage.
foreach ($fields as $field) {
if (!$field->deleted) {
\Drupal::entityManager()->onFieldStorageDefinitionDelete($field);
$field->deleted = TRUE;
}
}
// Unset static flag.
static::$inDeletion = FALSE;
}
/**
* {@inheritdoc}
*/
public function getSchema() {
if (!isset($this->schema)) {
// Get the schema from the field item class.
$class = $this->getFieldItemClass();
$schema = $class::schema($this);
// Fill in default values for optional entries.
$schema += array(
'unique keys' => array(),
'indexes' => array(),
'foreign keys' => array(),
);
// Merge custom indexes with those specified by the field type. Custom
// indexes prevail.
$schema['indexes'] = $this->indexes + $schema['indexes'];
$this->schema = $schema;
}
return $this->schema;
}
/**
* {@inheritdoc}
*/
public function hasCustomStorage() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isBaseField() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getColumns() {
$schema = $this->getSchema();
// A typical use case for the method is to iterate on the columns, while
// some other use cases rely on identifying the first column with the key()
// function. Since the schema is persisted in the Field object, we take care
// of resetting the array pointer so that the former does not interfere with
// the latter.
reset($schema['columns']);
return $schema['columns'];
}
/**
* {@inheritdoc}
*/
public function getBundles() {
if (!$this->isDeleted()) {
$map = \Drupal::entityManager()->getFieldMap();
if (isset($map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'])) {
return $map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'];
}
}
return array();
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->field_name;
}
/**
* {@inheritdoc}
*/
public function isDeleted() {
return $this->deleted;
}
/**
* {@inheritdoc}
*/
public function getTypeProvider() {
return $this->module;
}
/**
* {@inheritdoc}
*/
public function getType() {
return $this->type;
}
/**
* {@inheritdoc}
*/
public function getSettings() {
// @todo FieldTypePluginManager maintains its own static cache. However, do
// some CPU and memory profiling to see if it's worth statically caching
// $field_type_info, or the default field storage and field settings,
// within $this.
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$settings = $field_type_manager->getDefaultStorageSettings($this->getType());
return $this->settings + $settings;
}
/**
* {@inheritdoc}
*/
public function getSetting($setting_name) {
// @todo See getSettings() about potentially statically caching this.
// We assume here that one call to array_key_exists() is more efficient
// than calling getSettings() when all we need is a single setting.
if (array_key_exists($setting_name, $this->settings)) {
return $this->settings[$setting_name];
}
$settings = $this->getSettings();
if (array_key_exists($setting_name, $settings)) {
return $settings[$setting_name];
}
else {
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function setSetting($setting_name, $value) {
$this->settings[$setting_name] = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function setSettings(array $settings) {
$this->settings = $settings;
return $this;
}
/**
* {@inheritdoc}
*/
public function isTranslatable() {
return $this->translatable;
}
/**
* {@inheritdoc}
*/
public function isRevisionable() {
// All configurable fields are revisionable.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function setTranslatable($translatable) {
$this->translatable = $translatable;
return $this;
}
/**
* {@inheritdoc}
*/
public function getProvider() {
return 'field';
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->label();
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCardinality() {
return $this->cardinality;
}
/**
* {@inheritdoc}
*/
public function setCardinality($cardinality) {
$this->cardinality = $cardinality;
return $this;
}
/**
* {@inheritdoc}
*/
public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
// If the field item class implements the interface, create an orphaned
// runtime item object, so that it can be used as the options provider
// without modifying the entity being worked on.
if (is_subclass_of($this->getFieldItemClass(), '\Drupal\Core\TypedData\OptionsProviderInterface')) {
$items = $entity->get($this->getName());
return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
}
// @todo: Allow setting custom options provider, see
// https://www.drupal.org/node/2002138.
}
/**
* {@inheritdoc}
*/
public function isMultiple() {
$cardinality = $this->getCardinality();
return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
}
/**
* {@inheritdoc}
*/
public function isLocked() {
return $this->locked;
}
/**
* {@inheritdoc}
*/
public function setLocked($locked) {
$this->locked = $locked;
return $this;
}
/**
* {@inheritdoc}
*/
public function getTargetEntityTypeId() {
return $this->entity_type;
}
/**
* {@inheritdoc}
*/
public function isQueryable() {
return TRUE;
}
/**
* Determines whether a field has any data.
*
* @return bool
* TRUE if the field has data for any entity; FALSE otherwise.
*/
public function hasData() {
return \Drupal::entityManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
}
/**
* Implements the magic __sleep() method.
*
* Using the Serialize interface and serialize() / unserialize() methods
* breaks entity forms in PHP 5.4.
* @todo Investigate in https://www.drupal.org/node/2074253.
*/
public function __sleep() {
// Only serialize necessary properties, excluding those that can be
// recalculated.
$properties = get_object_vars($this);
unset($properties['schema'], $properties['propertyDefinitions'], $properties['original']);
return array_keys($properties);
}
/**
* {@inheritdoc}
*/
public function getConstraints() {
return array();
}
/**
* {@inheritdoc}
*/
public function getConstraint($constraint_name) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinition($name) {
if (!isset($this->propertyDefinitions)) {
$this->getPropertyDefinitions();
}
if (isset($this->propertyDefinitions[$name])) {
return $this->propertyDefinitions[$name];
}
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions() {
if (!isset($this->propertyDefinitions)) {
$class = $this->getFieldItemClass();
$this->propertyDefinitions = $class::propertyDefinitions($this);
}
return $this->propertyDefinitions;
}
/**
* {@inheritdoc}
*/
public function getPropertyNames() {
return array_keys($this->getPropertyDefinitions());
}
/**
* {@inheritdoc}
*/
public function getMainPropertyName() {
$class = $this->getFieldItemClass();
return $class::mainPropertyName();
}
/**
* {@inheritdoc}
*/
public function getUniqueStorageIdentifier() {
return $this->uuid();
}
/**
* Helper to retrieve the field item class.
*/
protected function getFieldItemClass() {
$type_definition = \Drupal::typedDataManager()
->getDefinition('field_item:' . $this->getType());
return $type_definition['class'];
}
/**
* Loads a field config entity based on the entity type and field name.
*
* @param string $entity_type_id
* ID of the entity type.
* @param string $field_name
* Name of the field.
*
* @return static
* The field config entity if one exists for the provided field name,
* otherwise NULL.
*/
public static function loadByName($entity_type_id, $field_name) {
return \Drupal::entityManager()->getStorage('field_storage_config')->load($entity_type_id . '.' . $field_name);
}
/**
* {@inheritdoc}
*/
public function isDeletable() {
// The field storage is not deleted, is configured to be removed when there
// are no fields, the field storage has no bundles, and field storages are
// not in the process of being deleted.
return !$this->deleted && !$this->persist_with_no_fields && count($this->getBundles()) == 0 && !static::$inDeletion;
}
/**
* {@inheritdoc}
*/
public function getIndexes() {
return $this->indexes;
}
/**
* {@inheritdoc}
*/
public function setIndexes(array $indexes) {
$this->indexes = $indexes;
return $this;
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\field\FieldConfigAccessControlHandler.
*/
namespace Drupal\field;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for the field entity type.
*
* @see \Drupal\field\Entity\FieldConfig
*/
class FieldConfigAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
if ($operation == 'delete') {
$field_storage_entity = $entity->getFieldStorageDefinition();
if ($field_storage_entity->isLocked()) {
return AccessResult::forbidden()->cacheUntilEntityChanges($field_storage_entity);
}
else {
return AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' fields')->cacheUntilEntityChanges($field_storage_entity);
}
}
return AccessResult::allowedIfHasPermission($account, 'administer ' . $entity->getTargetEntityTypeId() . ' fields');
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\field\FieldConfigInterface.
*/
namespace Drupal\field;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Provides an interface defining a field entity.
*/
interface FieldConfigInterface extends ConfigEntityInterface, FieldDefinitionInterface {
/**
* Gets the deleted flag of the field.
*
* @return bool
* Returns TRUE if the field is deleted.
*/
public function isDeleted();
}

View file

@ -0,0 +1,185 @@
<?php
/**
* @file
* Contains \Drupal\field\FieldConfigStorage.
*/
namespace Drupal\field;
use Drupal\Core\Config\Config;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldConfigStorageBase;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\State\StateInterface;
/**
* Controller class for fields.
*/
class FieldConfigStorage extends FieldConfigStorageBase {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The state keyvalue collection.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The field type plugin manager.
*
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
*/
protected $fieldTypeManager;
/**
* Constructs a FieldConfigStorage object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
* The UUID service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
* The field type plugin manager.
*/
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityManagerInterface $entity_manager, StateInterface $state, FieldTypePluginManagerInterface $field_type_manager) {
parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager);
$this->entityManager = $entity_manager;
$this->state = $state;
$this->fieldTypeManager = $field_type_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('config.factory'),
$container->get('uuid'),
$container->get('language_manager'),
$container->get('entity.manager'),
$container->get('state'),
$container->get('plugin.manager.field.field_type')
);
}
/**
* {@inheritdoc}
*/
public function importDelete($name, Config $new_config, Config $old_config) {
// If the field storage has been deleted in the same import, the field will
// be deleted by then, and there is nothing left to do. Just return TRUE so
// that the file does not get written to active store.
if (!$old_config->get()) {
return TRUE;
}
return parent::importDelete($name, $new_config, $old_config);
}
/**
* {@inheritdoc}
*/
public function loadByProperties(array $conditions = array()) {
// Include deleted fields if specified in the $conditions parameters.
$include_deleted = isset($conditions['include_deleted']) ? $conditions['include_deleted'] : FALSE;
unset($conditions['include_deleted']);
$fields = array();
// Get fields stored in configuration. If we are explicitly looking for
// deleted fields only, this can be skipped, because they will be
// retrieved from state below.
if (empty($conditions['deleted'])) {
if (isset($conditions['entity_type']) && isset($conditions['bundle']) && isset($conditions['field_name'])) {
// Optimize for the most frequent case where we do have a specific ID.
$id = $conditions['entity_type'] . '.' . $conditions['bundle'] . '.' . $conditions['field_name'];
$fields = $this->loadMultiple(array($id));
}
else {
// No specific ID, we need to examine all existing fields.
$fields = $this->loadMultiple();
}
}
// Merge deleted fields (stored in state) if needed.
if ($include_deleted || !empty($conditions['deleted'])) {
$deleted_fields = $this->state->get('field.field.deleted') ?: array();
$deleted_storages = $this->state->get('field.storage.deleted') ?: array();
foreach ($deleted_fields as $id => $config) {
// If the field storage itself is deleted, inject it directly in the field.
if (isset($deleted_storages[$config['field_storage_uuid']])) {
$config['field_storage'] = $this->entityManager->getStorage('field_storage_config')->create($deleted_storages[$config['field_storage_uuid']]);
}
$fields[$id] = $this->create($config);
}
}
// Collect matching fields.
$matching_fields = array();
foreach ($fields as $field) {
// Some conditions are checked against the field storage.
$field_storage = $field->getFieldStorageDefinition();
// Only keep the field if it matches all conditions.
foreach ($conditions as $key => $value) {
// Extract the actual value against which the condition is checked.
switch ($key) {
case 'field_name':
$checked_value = $field_storage->getName();
break;
case 'field_id':
case 'field_storage_uuid':
$checked_value = $field_storage->uuid();
break;
case 'uuid';
$checked_value = $field->uuid();
break;
case 'deleted';
$checked_value = $field->isDeleted();
break;
default:
$checked_value = $field->get($key);
break;
}
// Skip to the next field as soon as one condition does not match.
if ($checked_value != $value) {
continue 2;
}
}
// When returning deleted fields, key the results by UUID since they
// can include several fields with the same ID.
$key = $include_deleted ? $field->uuid() : $field->id();
$matching_fields[$key] = $field;
}
return $matching_fields;
}
}

View file

@ -0,0 +1,136 @@
<?php
/**
* @file
* Contains \Drupal\field\FieldStorageConfigInterface.
*/
namespace Drupal\field;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Provides an interface defining a field storage entity.
*/
interface FieldStorageConfigInterface extends ConfigEntityInterface, FieldStorageDefinitionInterface {
/**
* Returns the field type.
*
* @return string
* The field type, i.e. the id of a field type plugin. For example 'text'.
*/
public function getType();
/**
* Returns the name of the module providing the field type.
*
* @return string
* The name of the module that provides the field type.
*/
public function getTypeProvider();
/**
* Returns the list of bundles where the field storage has fields.
*
* @return array
* An array of bundle names.
*/
public function getBundles();
/**
* Returns whether the field is deleted or not.
*
* @return bool
* TRUE if the field is deleted.
*/
public function isDeleted();
/**
* Checks if the field storage can be deleted.
*
* @return bool
* TRUE if the field storage can be deleted.
*/
public function isDeletable();
/**
* Returns whether the field storage is locked or not.
*
* @return bool
* TRUE if the field storage is locked.
*/
public function isLocked();
/**
* Sets the locked flag.
*
* @param bool $locked
* Sets value of locked flag.
*
* @return $this
*/
public function setLocked($locked);
/**
* Sets the maximum number of items allowed for the field.
*
* @param int $cardinality
* The cardinality value.
*
* @return $this
*/
public function setCardinality($cardinality);
/**
* Sets the value for a field setting by name.
*
* @param string $setting_name
* The name of the setting.
* @param mixed $value
* The value of the setting.
*
* @return $this
*/
public function setSetting($setting_name, $value);
/**
* Sets field settings (overwrites existing settings).
*
* @param array $settings
* The array of field settings.
*
* @return $this
*/
public function setSettings(array $settings);
/**
* Sets whether the field is translatable.
*
* @param bool $translatable
* Whether the field is translatable.
*
* @return $this
*/
public function setTranslatable($translatable);
/**
* Returns the custom storage indexes for the field data storage.
*
* @return array
* An array of custom indexes.
*/
public function getIndexes();
/**
* Sets the custom storage indexes for the field data storage..
*
* @param array $indexes
* The array of custom indexes.
*
* @return $this
*/
public function setIndexes(array $indexes);
}

View file

@ -0,0 +1,175 @@
<?php
/**
* @file
* Contains \Drupal\field\FieldStorageConfigStorage.
*/
namespace Drupal\field;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\State\StateInterface;
/**
* Controller class for "field storage" configuration entities.
*/
class FieldStorageConfigStorage extends ConfigEntityStorage {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The state keyvalue collection.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The field type plugin manager.
*
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
*/
protected $fieldTypeManager;
/**
* Constructs a FieldStorageConfigStorage object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
* The UUID service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
* @param \Drupal\Component\Plugin\PluginManagerInterface\FieldTypePluginManagerInterface
* The field type plugin manager.
*/
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, StateInterface $state, FieldTypePluginManagerInterface $field_type_manager) {
parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager);
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
$this->state = $state;
$this->fieldTypeManager = $field_type_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('config.factory'),
$container->get('uuid'),
$container->get('language_manager'),
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('state'),
$container->get('plugin.manager.field.field_type')
);
}
/**
* {@inheritdoc}
*/
public function loadByProperties(array $conditions = array()) {
// Include deleted fields if specified in the $conditions parameters.
$include_deleted = isset($conditions['include_deleted']) ? $conditions['include_deleted'] : FALSE;
unset($conditions['include_deleted']);
/** @var \Drupal\field\FieldStorageConfigInterface[] $storages */
$storages = array();
// Get field storages living in configuration. If we are explicitly looking
// for deleted storages only, this can be skipped, because they will be
// retrieved from state below.
if (empty($conditions['deleted'])) {
if (isset($conditions['entity_type']) && isset($conditions['field_name'])) {
// Optimize for the most frequent case where we do have a specific ID.
$id = $conditions['entity_type'] . $conditions['field_name'];
$storages = $this->loadMultiple(array($id));
}
else {
// No specific ID, we need to examine all existing storages.
$storages = $this->loadMultiple();
}
}
// Merge deleted field storages (living in state) if needed.
if ($include_deleted || !empty($conditions['deleted'])) {
$deleted_storages = $this->state->get('field.storage.deleted') ?: array();
foreach ($deleted_storages as $id => $config) {
$storages[$id] = $this->create($config);
}
}
// Collect matching fields.
$matches = array();
foreach ($storages as $field) {
foreach ($conditions as $key => $value) {
// Extract the actual value against which the condition is checked.
$checked_value = $field->get($key);
// Skip to the next field as soon as one condition does not match.
if ($checked_value != $value) {
continue 2;
}
}
// When returning deleted fields, key the results by UUID since they can
// include several fields with the same ID.
$key = $include_deleted ? $field->uuid() : $field->id();
$matches[$key] = $field;
}
return $matches;
}
/**
* {@inheritdoc}
*/
protected function mapFromStorageRecords(array $records) {
foreach ($records as &$record) {
$class = $this->fieldTypeManager->getPluginClass($record['type']);
$record['settings'] = $class::storageSettingsFromConfigData($record['settings']);
}
return parent::mapFromStorageRecords($records);
}
/**
* {@inheritdoc}
*/
protected function mapToStorageRecord(EntityInterface $entity) {
$record = parent::mapToStorageRecord($entity);
$class = $this->fieldTypeManager->getPluginClass($record['type']);
$record['settings'] = $class::storageSettingsToConfigData($record['settings']);
return $record;
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* @file
* Contains \Drupal\field\FieldStorageConfigUpdateForbiddenException.
*/
namespace Drupal\field;
use Drupal\Core\Field\FieldException;
/**
* Exception class thrown by hook_field_storage_config_update_forbid().
*/
class FieldStorageConfigUpdateForbiddenException extends FieldException {}

View file

@ -0,0 +1,80 @@
<?php
/**
* @file
* Contains \Drupal\field\FieldUninstallValidator.
*/
namespace Drupal\field;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Prevents uninstallation of modules providing active field storage.
*/
class FieldUninstallValidator implements ModuleUninstallValidatorInterface {
use StringTranslationTrait;
/**
* The field storage config storage.
*
* @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
*/
protected $fieldStorageConfigStorage;
/**
* Constructs a new FieldUninstallValidator.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
*/
public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
$this->fieldStorageConfigStorage = $entity_manager->getStorage('field_storage_config');
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public function validate($module) {
$reasons = [];
if ($field_storages = $this->getFieldStoragesByModule($module)) {
// Provide an explanation message (only mention pending deletions if there
// remain no actual, non-deleted fields.)
$non_deleted = FALSE;
foreach ($field_storages as $field_storage) {
if (!$field_storage->isDeleted()) {
$non_deleted = TRUE;
break;
}
}
if ($non_deleted) {
$reasons[] = $this->t('Fields type(s) in use');
}
else {
$reasons[] = $this->t('Fields pending deletion');
}
}
return $reasons;
}
/**
* Returns all field storages for a specified module.
*
* @param string $module
* The module to filter field storages by.
*
* @return \Drupal\field\FieldStorageConfigInterface[]
* An array of field storages for a specified module.
*/
protected function getFieldStoragesByModule($module) {
return $this->fieldStorageConfigStorage->loadByProperties(['module' => $module, 'include_deleted' => TRUE]);
}
}

View file

@ -0,0 +1,185 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Boolean\BooleanFieldTest.
*/
namespace Drupal\field\Tests\Boolean;
use Drupal\Component\Utility\Unicode;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\simpletest\WebTestBase;
/**
* Tests boolean field functionality.
*
* @group field
*/
class BooleanFieldTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_test', 'field_ui', 'options');
/**
* A field to use in this test class.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field used in this test class.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(array(
'view test entity',
'administer entity_test content',
'administer entity_test form display',
'administer entity_test fields',
)));
}
/**
* Tests boolean field.
*/
function testBooleanField() {
$on = $this->randomMachineName();
$off = $this->randomMachineName();
$label = $this->randomMachineName();
// Create a field with settings to validate.
$field_name = Unicode::strtolower($this->randomMachineName());
$this->fieldStorage = FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'boolean',
));
$this->fieldStorage->save();
$this->field = FieldConfig::create(array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'label' => $label,
'required' => TRUE,
'settings' => array(
'on_label' => $on,
'off_label' => $off,
),
));
$this->field->save();
// Create a form display for the default form mode.
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'boolean_checkbox',
))
->save();
// Create a display for the full view mode.
entity_get_display('entity_test', 'entity_test', 'full')
->setComponent($field_name, array(
'type' => 'boolean',
))
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[value]", '', 'Widget found.');
$this->assertRaw($on);
// Submit and ensure it is accepted.
$edit = array(
"{$field_name}[value]" => 1,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
// Verify that boolean value is displayed.
$entity = entity_load('entity_test', $id);
$display = entity_get_display($entity->getEntityTypeId(), $entity->bundle(), 'full');
$content = $display->build($entity);
$this->setRawContent(\Drupal::service('renderer')->renderRoot($content));
$this->assertRaw('<div class="field-item">' . $on . '</div>');
// Test if we can change the on label.
$on = $this->randomMachineName();
$edit = array(
'settings[on_label]' => $on,
);
$this->drupalPostForm('entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name, $edit, t('Save settings'));
// Check if we see the updated labels in the creation form.
$this->drupalGet('entity_test/add');
$this->assertRaw($on);
// Test the display_label option.
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'boolean_checkbox',
'settings' => array(
'display_label' => TRUE,
)
))
->save();
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[value]", '', 'Widget found.');
$this->assertNoRaw($on);
$this->assertText($this->field->label());
// Go to the form display page and check if the default settings works as
// expected.
$fieldEditUrl = 'entity_test/structure/entity_test/form-display';
$this->drupalGet($fieldEditUrl);
// Click on the widget settings button to open the widget settings form.
$this->drupalPostAjaxForm(NULL, array(), $field_name . "_settings_edit");
$this->assertText(
'Use field label instead of the "On label" as label',
t('Display setting checkbox available.')
);
// Enable setting.
$edit = array('fields[' . $field_name . '][settings_edit_form][settings][display_label]' => 1);
$this->drupalPostAjaxForm(NULL, $edit, $field_name . "_plugin_settings_update");
$this->drupalPostForm(NULL, NULL, 'Save');
// Go again to the form display page and check if the setting
// is stored and has the expected effect.
$this->drupalGet($fieldEditUrl);
$this->assertText('Use field label: Yes', 'Checking the display settings checkbox updated the value.');
$this->drupalPostAjaxForm(NULL, array(), $field_name . "_settings_edit");
$this->assertText(
'Use field label instead of the "On label" as label',
t('Display setting checkbox is available')
);
$this->assertFieldByXPath(
'*//input[starts-with(@id, "edit-fields-' . $field_name . '-settings-edit-form-settings-display-label") and @value="1"]',
TRUE,
t('Display label changes label of the checkbox')
);
// Test the boolean field settings.
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name);
$this->assertFieldById('edit-settings-on-label', $on);
$this->assertFieldById('edit-settings-off-label', $off);
}
}

View file

@ -0,0 +1,132 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Boolean\BooleanFormatterSettingsTest.
*/
namespace Drupal\field\Tests\Boolean;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\WebTestBase;
/**
* Tests the Boolean field formatter settings.
*
* @group field
*/
class BooleanFormatterSettingsTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['field', 'field_ui', 'text', 'node', 'user'];
/**
* The name of the entity bundle that is created in the test.
*
* @var string
*/
protected $bundle;
/**
* The name of the Boolean field to use for testing.
*
* @var string
*/
protected $fieldName;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a content type. Use Node because it has Field UI pages that work.
$type_name = Unicode::strtolower($this->randomMachineName(8)) . '_test';
$type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name));
$this->bundle = $type->id();
$admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node display', 'bypass node access', 'administer nodes'));
$this->drupalLogin($admin_user);
$this->fieldName = Unicode::strtolower($this->randomMachineName(8));
$field_storage = FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => 'boolean',
]);
$field_storage->save();
$instance = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $this->bundle,
'label' => $this->randomMachineName(),
]);
$instance->save();
$display = entity_get_display('node', $this->bundle, 'default')
->setComponent($this->fieldName, [
'type' => 'boolean',
'settings' => [],
]);
$display->save();
}
/**
* Tests the formatter settings page for the Boolean formatter.
*/
function testBooleanFormatterSettings() {
// List the options we expect to see on the settings form. Omit the one
// with the Unicode check/x characters, which does not appear to work
// well in WebTestBase.
$options = array(
'Yes / No',
'True / False',
'On / Off',
'Enabled / Disabled',
'1 / 0',
'Custom',
);
// Define what the "default" option should look like, depending on the
// field settings.
$default = 'Field settings (@on / @off)';
// For several different values of the field settings, test that the
// options, including default, are shown correctly.
$settings = array(
array('Yes', 'No'),
array('On', 'Off'),
array('TRUE', 'FALSE'),
);
foreach ($settings as $values) {
// Set up the field settings.
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/fields/node.' . $this->bundle . '.' . $this->fieldName);
$this->drupalPostForm(NULL, array(
'settings[on_label]' => $values[0],
'settings[off_label]' => $values[1],
), 'Save settings');
// Open the Manage Display page and trigger the field settings form.
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display');
$this->drupalPostAjaxForm(NULL, array(), $this->fieldName . '_settings_edit');
// Test that the settings options are present in the correct format.
foreach ($options as $string) {
$this->assertText($string);
}
$this->assertText(SafeMarkup::format($default, array('@on' => $values[0], '@off' => $values[1])));
}
}
}

View file

@ -0,0 +1,144 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Boolean\BooleanFormatterTest.
*/
namespace Drupal\field\Tests\Boolean;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the boolean formatter.
*
* @group field
*/
class BooleanFormatterTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['field', 'text', 'entity_test', 'user'];
/**
* @var string
*/
protected $entityType;
/**
* @var string
*/
protected $bundle;
/**
* @var string
*/
protected $fieldName;
/**
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
*/
protected $display;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(['field']);
$this->installEntitySchema('entity_test');
$this->entityType = 'entity_test';
$this->bundle = $this->entityType;
$this->fieldName = Unicode::strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => $this->entityType,
'type' => 'boolean',
]);
$field_storage->save();
$instance = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $this->bundle,
'label' => $this->randomMachineName(),
]);
$instance->save();
$this->display = entity_get_display($this->entityType, $this->bundle, 'default')
->setComponent($this->fieldName, [
'type' => 'boolean',
'settings' => [],
]);
$this->display->save();
}
/**
* Renders fields of a given entity with a given display.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity object with attached fields to render.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The display to render the fields in.
*
* @return string
* The rendered entity fields.
*/
protected function renderEntityFields(FieldableEntityInterface $entity, EntityViewDisplayInterface $display) {
$content = $display->build($entity);
$content = $this->render($content);
return $content;
}
/**
* Tests boolean formatter output.
*/
public function testBooleanFormatter() {
$data = [];
$data[] = [0, [], 'Off'];
$data[] = [1, [], 'On'];
$format = ['format' => 'enabled-disabled'];
$data[] = [0, $format, 'Disabled'];
$data[] = [1, $format, 'Enabled'];
$format = ['format' => 'unicode-yes-no'];
$data[] = [1, $format, '✔'];
$data[] = [0, $format, '✖'];
$format = [
'format' => 'custom',
'format_custom_false' => 'FALSE',
'format_custom_true' => 'TRUE'
];
$data[] = [0, $format, 'FALSE'];
$data[] = [1, $format, 'TRUE'];
foreach ($data as $test_data) {
list($value, $settings, $expected) = $test_data;
$component = $this->display->getComponent($this->fieldName);
$component['settings'] = $settings;
$this->display->setComponent($this->fieldName, $component);
$entity = EntityTest::create([]);
$entity->{$this->fieldName}->value = $value;
// Verify that all HTML is escaped and newlines are retained.
$this->renderEntityFields($entity, $this->display);
$this->assertRaw($expected);
}
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Boolean\BooleanItemTest.
*/
namespace Drupal\field\Tests\Boolean;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\field\Tests\FieldUnitTestBase;
/**
* Tests the new entity API for the boolean field type.
*
* @group field
*/
class BooleanItemTest extends FieldUnitTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a boolean field and storage for validation.
entity_create('field_storage_config', array(
'field_name' => 'field_boolean',
'entity_type' => 'entity_test',
'type' => 'boolean',
))->save();
entity_create('field_config', array(
'entity_type' => 'entity_test',
'field_name' => 'field_boolean',
'bundle' => 'entity_test',
))->save();
// Create a form display for the default form mode.
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent('field_boolean', array(
'type' => 'boolean_checkbox',
))
->save();
}
/**
* Tests using entity fields of the boolean field type.
*/
public function testBooleanItem() {
// Verify entity creation.
$entity = entity_create('entity_test');
$value = '1';
$entity->field_boolean = $value;
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertTrue($entity->field_boolean instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_boolean[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_boolean->value, $value);
$this->assertEqual($entity->field_boolean[0]->value, $value);
// Verify changing the boolean value.
$new_value = 0;
$entity->field_boolean->value = $new_value;
$this->assertEqual($entity->field_boolean->value, $new_value);
// Read changed entity and assert changed values.
$entity->save();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->field_boolean->value, $new_value);
// Test sample item generation.
$entity = entity_create('entity_test');
$entity->field_boolean->generateSampleItems();
$this->entityValidateAndSave($entity);
}
}

View file

@ -0,0 +1,361 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\BulkDeleteTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\FieldConfig;
/**
* Bulk delete storages and fields, and clean up afterwards.
*
* @group field
*/
class BulkDeleteTest extends FieldUnitTestBase {
/**
* The fields to use in this test.
*
* @var array
*/
protected $fieldStorages;
/**
* The entities to use in this test.
*
* @var array
*/
protected $entities;
/**
* The entities to use in this test, keyed by bundle.
*
* @var array
*/
protected $entitiesByBundles;
/**
* The bundles for the entities used in this test.
*
* @var array
*/
protected $bundles;
/**
* The entity type to be used in the test classes.
*
* @var string
*/
protected $entityTypeId = 'entity_test';
/**
* Tests that the expected hooks have been invoked on the expected entities.
*
* @param $expected_hooks
* An array keyed by hook name, with one entry per expected invocation.
* Each entry is the value of the "$entity" parameter the hook is expected
* to have been passed.
* @param $actual_hooks
* The array of actual hook invocations recorded by field_test_memorize().
*/
function checkHooksInvocations($expected_hooks, $actual_hooks) {
foreach ($expected_hooks as $hook => $invocations) {
$actual_invocations = $actual_hooks[$hook];
// Check that the number of invocations is correct.
$this->assertEqual(count($actual_invocations), count($invocations), "$hook() was called the expected number of times.");
// Check that the hook was called for each expected argument.
foreach ($invocations as $argument) {
$found = FALSE;
foreach ($actual_invocations as $actual_arguments) {
// The argument we are looking for is either an array of entities as
// the second argument or a single entity object as the first.
if ($argument instanceof EntityInterface && $actual_arguments[0]->id() == $argument->id()) {
$found = TRUE;
break;
}
// In case of an array, compare the array size and make sure it
// contains the same elements.
elseif (is_array($argument) && count($actual_arguments[1]) == count($argument) && count(array_diff_key($actual_arguments[1], $argument)) == 0) {
$found = TRUE;
break;
}
}
$this->assertTrue($found, "$hook() was called on expected argument");
}
}
}
protected function setUp() {
parent::setUp();
$this->fieldStorages = array();
$this->entities = array();
$this->entitiesByBundles = array();
// Create two bundles.
$this->bundles = array('bb_1' => 'bb_1', 'bb_2' => 'bb_2');
foreach ($this->bundles as $name => $desc) {
entity_test_create_bundle($name, $desc);
}
// Create two field storages.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'bf_1',
'entity_type' => $this->entityTypeId,
'type' => 'test_field',
'cardinality' => 1
));
$field_storage->save();
$this->fieldStorages[] = $field_storage;
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'bf_2',
'entity_type' => $this->entityTypeId,
'type' => 'test_field',
'cardinality' => 4
));
$field_storage->save();
$this->fieldStorages[] = $field_storage;
// For each bundle, create each field, and 10 entities with values for the
// fields.
foreach ($this->bundles as $bundle) {
foreach ($this->fieldStorages as $field_storage) {
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => $bundle,
))->save();
}
for ($i = 0; $i < 10; $i++) {
$entity = entity_create($this->entityTypeId, array('type' => $bundle));
foreach ($this->fieldStorages as $field_storage) {
$entity->{$field_storage->getName()}->setValue($this->_generateTestFieldValues($field_storage->getCardinality()));
}
$entity->save();
}
}
$this->entities = entity_load_multiple($this->entityTypeId);
foreach ($this->entities as $entity) {
// This test relies on the entities having stale field definitions
// so that the deleted field can be accessed on them. Access the field
// now, so that they are always loaded.
$entity->bf_1->value;
// Also keep track of the entities per bundle.
$this->entitiesByBundles[$entity->bundle()][$entity->id()] = $entity;
}
}
/**
* Verify that deleting a field leaves the field data items in the database
* and that the appropriate Field API functions can operate on the deleted
* data and field definition.
*
* This tests how EntityFieldQuery interacts with field deletion and could be
* moved to FieldCrudTestCase, but depends on this class's setUp().
*/
function testDeleteField() {
$bundle = reset($this->bundles);
$field_storage = reset($this->fieldStorages);
$field_name = $field_storage->getName();
$factory = \Drupal::service('entity.query');
// There are 10 entities of this bundle.
$found = $factory->get('entity_test')
->condition('type', $bundle)
->execute();
$this->assertEqual(count($found), 10, 'Correct number of entities found before deleting');
// Delete the field.
$field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
$field->delete();
// The field still exists, deleted.
$fields = entity_load_multiple_by_properties('field_config', array('field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE));
$this->assertEqual(count($fields), 1, 'There is one deleted field');
$field = $fields[$field->uuid()];
$this->assertEqual($field->getTargetBundle(), $bundle, 'The deleted field is for the correct bundle');
// Check that the actual stored content did not change during delete.
$storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $storage->getTableMapping();
$table = $table_mapping->getDedicatedDataTableName($field_storage);
$column = $table_mapping->getFieldColumnName($field_storage, 'value');
$result = db_select($table, 't')
->fields('t')
->execute();
foreach ($result as $row) {
$this->assertEqual($this->entities[$row->entity_id]->{$field_name}->value, $row->$column);
}
// There are 0 entities of this bundle with non-deleted data.
$found = $factory->get('entity_test')
->condition('type', $bundle)
->condition("$field_name.deleted", 0)
->execute();
$this->assertFalse($found, 'No entities found after deleting');
// There are 10 entities of this bundle when deleted fields are allowed, and
// their values are correct.
$found = $factory->get('entity_test')
->condition('type', $bundle)
->condition("$field_name.deleted", 1)
->sort('id')
->execute();
$this->assertEqual(count($found), 10, 'Correct number of entities found after deleting');
$this->assertFalse(array_diff($found, array_keys($this->entities)));
}
/**
* Verify that field data items and fields are purged when a field storage is
* deleted.
*/
function testPurgeField() {
// Start recording hook invocations.
field_test_memorize();
$bundle = reset($this->bundles);
$field_storage = reset($this->fieldStorages);
$field_name = $field_storage->getName();
// Delete the field.
$field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
$field->delete();
// No field hooks were called.
$mem = field_test_memorize();
$this->assertEqual(count($mem), 0, 'No field hooks were called');
$batch_size = 2;
for ($count = 8; $count >= 0; $count -= $batch_size) {
// Purge two entities.
field_purge_batch($batch_size);
// There are $count deleted entities left.
$found = \Drupal::entityQuery('entity_test')
->condition('type', $bundle)
->condition($field_name . '.deleted', 1)
->execute();
$this->assertEqual(count($found), $count, 'Correct number of entities found after purging 2');
}
// Check hooks invocations.
// FieldItemInterface::delete() should have been called once for each entity in the
// bundle.
$actual_hooks = field_test_memorize();
$hooks = array();
$entities = $this->entitiesByBundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_delete'][] = $entity;
}
$this->checkHooksInvocations($hooks, $actual_hooks);
// The field still exists, deleted.
$fields = entity_load_multiple_by_properties('field_config', array('field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE));
$this->assertEqual(count($fields), 1, 'There is one deleted field');
// Purge the field.
field_purge_batch($batch_size);
// The field is gone.
$fields = entity_load_multiple_by_properties('field_config', array('field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE));
$this->assertEqual(count($fields), 0, 'The field is gone');
// The field storage still exists, not deleted, because it has a second
// field.
$storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $field_storage->uuid(), 'include_deleted' => TRUE));
$this->assertTrue(isset($storages[$field_storage->uuid()]), 'The field storage exists and is not deleted');
}
/**
* Verify that field storages are preserved and purged correctly as multiple
* fields are deleted and purged.
*/
function testPurgeFieldStorage() {
// Start recording hook invocations.
field_test_memorize();
$field_storage = reset($this->fieldStorages);
$field_name = $field_storage->getName();
// Delete the first field.
$bundle = reset($this->bundles);
$field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
$field->delete();
// Assert that FieldItemInterface::delete() was not called yet.
$mem = field_test_memorize();
$this->assertEqual(count($mem), 0, 'No field hooks were called.');
// Purge the data.
field_purge_batch(10);
// Check hooks invocations.
// FieldItemInterface::delete() should have been called once for each entity in the
// bundle.
$actual_hooks = field_test_memorize();
$hooks = array();
$entities = $this->entitiesByBundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_delete'][] = $entity;
}
$this->checkHooksInvocations($hooks, $actual_hooks);
// The field still exists, deleted.
$fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE));
$this->assertTrue(isset($fields[$field->uuid()]) && $fields[$field->uuid()]->isDeleted(), 'The field exists and is deleted');
// Purge again to purge the field.
field_purge_batch(0);
// The field is gone.
$fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE));
$this->assertEqual(count($fields), 0, 'The field is purged.');
// The field storage still exists, not deleted.
$storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $field_storage->uuid(), 'include_deleted' => TRUE));
$this->assertTrue(isset($storages[$field_storage->uuid()]) && !$storages[$field_storage->uuid()]->isDeleted(), 'The field storage exists and is not deleted');
// Delete the second field.
$bundle = next($this->bundles);
$field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
$field->delete();
// Assert that FieldItemInterface::delete() was not called yet.
$mem = field_test_memorize();
$this->assertEqual(count($mem), 0, 'No field hooks were called.');
// Purge the data.
field_purge_batch(10);
// Check hooks invocations (same as above, for the 2nd bundle).
$actual_hooks = field_test_memorize();
$hooks = array();
$entities = $this->entitiesByBundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_delete'][] = $entity;
}
$this->checkHooksInvocations($hooks, $actual_hooks);
// The field and the storage still exist, deleted.
$fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE));
$this->assertTrue(isset($fields[$field->uuid()]) && $fields[$field->uuid()]->isDeleted(), 'The field exists and is deleted');
$storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $field_storage->uuid(), 'include_deleted' => TRUE));
$this->assertTrue(isset($storages[$field_storage->uuid()]) && $storages[$field_storage->uuid()]->isDeleted(), 'The field storage exists and is deleted');
// Purge again to purge the field and the storage.
field_purge_batch(0);
// The field and the storage are gone.
$fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE));
$this->assertEqual(count($fields), 0, 'The field is purged.');
$storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $field_storage->uuid(), 'include_deleted' => TRUE));
$this->assertEqual(count($storages), 0, 'The field storage is purged.');
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\ConfigFieldDefinitionTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Tests exposing field definitions for configurable fields.
*
* @group field
*/
class ConfigFieldDefinitionTest extends FieldUnitTestBase {
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface;
*/
protected $entityManager;
/**
* @var string
*/
private $entityType;
/**
* @var string
*/
private $bundle;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a field and a storage of type 'test_field', on the 'entity_test'
// entity type.
$this->entityType = 'entity_test';
$this->bundle = 'entity_test';
$this->createFieldWithStorage('', $this->entityType, $this->bundle);
$this->entityManager = $this->container->get('entity.manager');
// Create a second field on 'entity_test_rev'.
$this->createFieldWithStorage('_rev', 'entity_test_rev', 'entity_test_rev');
}
/**
* Makes sure a field definition is exposed for a configurable field.
*/
public function testBundleFieldDefinition() {
$definitions = $this->entityManager->getFieldDefinitions($this->entityType, $this->bundle);
$this->assertTrue(isset($definitions[$this->fieldTestData->field->getName()]));
$this->assertTrue($definitions[$this->fieldTestData->field->getName()] instanceof FieldDefinitionInterface);
// Make sure fields on other entity types are not exposed.
$this->assertFalse(isset($definitions[$this->fieldTestData->field_rev->getName()]));
}
/**
* Makes sure a field storage definition is exposed for a configurable field.
*/
public function testFieldStorageDefinition() {
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityType);
$this->assertTrue(isset($field_storage_definitions[$this->fieldTestData->field->getName()]));
$this->assertTrue($field_storage_definitions[$this->fieldTestData->field->getName()] instanceof FieldStorageDefinitionInterface);
// Make sure storages on other entity types are not exposed.
$this->assertFalse(isset($field_storage_definitions[$this->fieldTestData->field_rev->getName()]));
}
}

View file

@ -0,0 +1,305 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\DisplayApiTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Entity\Entity\EntityViewMode;
/**
* Tests the field display API.
*
* @group field
*/
class DisplayApiTest extends FieldUnitTestBase {
/**
* The field name to use in this test.
*
* @var string
*/
protected $fieldName;
/**
* The field label to use in this test.
*
* @var string
*/
protected $label;
/**
* The field cardinality to use in this test.
*
* @var number
*/
protected $cardinality;
/**
* The field display options to use in this test.
*
* @var array
*/
protected $displayOptions;
/**
* The test entity.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* An array of random values, in the format expected for field values.
*
* @var array
*/
protected $values;
protected function setUp() {
parent::setUp();
// Create a field and its storage.
$this->fieldName = 'test_field';
$this->label = $this->randomMachineName();
$this->cardinality = 4;
$field_storage = array(
'field_name' => $this->fieldName,
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => $this->cardinality,
);
$field = array(
'field_name' => $this->fieldName,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'label' => $this->label,
);
$this->displayOptions = array(
'default' => array(
'type' => 'field_test_default',
'settings' => array(
'test_formatter_setting' => $this->randomMachineName(),
),
),
'teaser' => array(
'type' => 'field_test_default',
'settings' => array(
'test_formatter_setting' => $this->randomMachineName(),
),
),
);
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $field)->save();
// Create a display for the default view mode.
entity_get_display($field['entity_type'], $field['bundle'], 'default')
->setComponent($this->fieldName, $this->displayOptions['default'])
->save();
// Create a display for the teaser view mode.
EntityViewMode::create(array('id' => 'entity_test.teaser', 'targetEntityType' => 'entity_test'))->save();
entity_get_display($field['entity_type'], $field['bundle'], 'teaser')
->setComponent($this->fieldName, $this->displayOptions['teaser'])
->save();
// Create an entity with values.
$this->values = $this->_generateTestFieldValues($this->cardinality);
$this->entity = entity_create('entity_test');
$this->entity->{$this->fieldName}->setValue($this->values);
$this->entity->save();
}
/**
* Tests the FieldItemListInterface::view() method.
*/
function testFieldItemListView() {
$items = $this->entity->get($this->fieldName);
// No display settings: check that default display settings are used.
$build = $items->view();
$this->render($build);
$settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('field_test_default');
$setting = $settings['test_formatter_setting'];
$this->assertText($this->label, 'Label was displayed.');
foreach ($this->values as $delta => $value) {
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Display settings: Check hidden field.
$display = array(
'label' => 'hidden',
'type' => 'field_test_multiple',
'settings' => array(
'test_formatter_setting_multiple' => $this->randomMachineName(),
'alter' => TRUE,
),
);
$build = $items->view($display);
$this->render($build);
$setting = $display['settings']['test_formatter_setting_multiple'];
$this->assertNoText($this->label, 'Label was not displayed.');
$this->assertText('field_test_entity_display_build_alter', 'Alter fired, display passed.');
$this->assertText('entity language is en', 'Language is placed onto the context.');
$array = array();
foreach ($this->values as $delta => $value) {
$array[] = $delta . ':' . $value['value'];
}
$this->assertText($setting . '|' . implode('|', $array), 'Values were displayed with expected setting.');
// Display settings: Check visually_hidden field.
$display = array(
'label' => 'visually_hidden',
'type' => 'field_test_multiple',
'settings' => array(
'test_formatter_setting_multiple' => $this->randomMachineName(),
'alter' => TRUE,
),
);
$build = $items->view($display);
$this->render($build);
$setting = $display['settings']['test_formatter_setting_multiple'];
$this->assertRaw('visually-hidden', 'Label was visually hidden.');
$this->assertText('field_test_entity_display_build_alter', 'Alter fired, display passed.');
$this->assertText('entity language is en', 'Language is placed onto the context.');
$array = array();
foreach ($this->values as $delta => $value) {
$array[] = $delta . ':' . $value['value'];
}
$this->assertText($setting . '|' . implode('|', $array), 'Values were displayed with expected setting.');
// Check the prepare_view steps are invoked.
$display = array(
'label' => 'hidden',
'type' => 'field_test_with_prepare_view',
'settings' => array(
'test_formatter_setting_additional' => $this->randomMachineName(),
),
);
$build = $items->view($display);
$this->render($build);
$setting = $display['settings']['test_formatter_setting_additional'];
$this->assertNoText($this->label, 'Label was not displayed.');
$this->assertNoText('field_test_entity_display_build_alter', 'Alter not fired.');
foreach ($this->values as $delta => $value) {
$this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// View mode: check that display settings specified in the display object
// are used.
$build = $items->view('teaser');
$this->render($build);
$setting = $this->displayOptions['teaser']['settings']['test_formatter_setting'];
$this->assertText($this->label, 'Label was displayed.');
foreach ($this->values as $delta => $value) {
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Unknown view mode: check that display settings for 'default' view mode
// are used.
$build = $items->view('unknown_view_mode');
$this->render($build);
$setting = $this->displayOptions['default']['settings']['test_formatter_setting'];
$this->assertText($this->label, 'Label was displayed.');
foreach ($this->values as $delta => $value) {
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
}
/**
* Tests the FieldItemInterface::view() method.
*/
function testFieldItemView() {
// No display settings: check that default display settings are used.
$settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('field_test_default');
$setting = $settings['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->fieldName}[$delta];
$build = $item->view();
$this->render($build);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Check that explicit display settings are used.
$display = array(
'type' => 'field_test_multiple',
'settings' => array(
'test_formatter_setting_multiple' => $this->randomMachineName(),
),
);
$setting = $display['settings']['test_formatter_setting_multiple'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->fieldName}[$delta];
$build = $item->view($display);
$this->render($build);
$this->assertText($setting . '|0:' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Check that prepare_view steps are invoked.
$display = array(
'type' => 'field_test_with_prepare_view',
'settings' => array(
'test_formatter_setting_additional' => $this->randomMachineName(),
),
);
$setting = $display['settings']['test_formatter_setting_additional'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->fieldName}[$delta];
$build = $item->view($display);
$this->render($build);
$this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// View mode: check that display settings specified in the field are used.
$setting = $this->displayOptions['teaser']['settings']['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->fieldName}[$delta];
$build = $item->view('teaser');
$this->render($build);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
// Unknown view mode: check that display settings for 'default' view mode
// are used.
$setting = $this->displayOptions['default']['settings']['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->fieldName}[$delta];
$build = $item->view('unknown_view_mode');
$this->render($build);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
}
/**
* Tests that the prepareView() formatter method still fires for empty values.
*/
function testFieldEmpty() {
// Uses \Drupal\field_test\Plugin\Field\FieldFormatter\TestFieldEmptyFormatter.
$display = array(
'label' => 'hidden',
'type' => 'field_empty_test',
'settings' => array(
'test_empty_string' => '**EMPTY FIELD**' . $this->randomMachineName(),
),
);
// $this->entity is set by the setUp() method and by default contains 4
// numeric values. We only want to test the display of this one field.
$build = $this->entity->get($this->fieldName)->view($display);
$this->render($build);
// The test field by default contains values, so should not display the
// default "empty" text.
$this->assertNoText($display['settings']['test_empty_string']);
// Now remove the values from the test field and retest.
$this->entity->{$this->fieldName} = array();
$this->entity->save();
$build = $this->entity->get($this->fieldName)->view($display);
$this->render($build);
// This time, as the field values have been removed, we *should* show the
// default "empty" text.
$this->assertText($display['settings']['test_empty_string']);
}
}

View file

@ -0,0 +1,109 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Email\EmailFieldTest.
*/
namespace Drupal\field\Tests\Email;
use Drupal\Component\Utility\Unicode;
use Drupal\simpletest\WebTestBase;
/**
* Tests email field functionality.
*
* @group field
*/
class EmailFieldTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'entity_test', 'field_ui');
/**
* A field storage to use in this test class.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field used in this test class.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(array(
'view test entity',
'administer entity_test content',
'administer content types',
)));
}
/**
* Tests email field.
*/
function testEmailField() {
// Create a field with settings to validate.
$field_name = Unicode::strtolower($this->randomMachineName());
$this->fieldStorage = entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'email',
));
$this->fieldStorage->save();
$this->field = entity_create('field_config', array(
'field_storage' => $this->fieldStorage,
'bundle' => 'entity_test',
));
$this->field->save();
// Create a form display for the default form mode.
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'email_default',
'settings' => array(
'placeholder' => 'example@example.com',
),
))
->save();
// Create a display for the full view mode.
entity_get_display('entity_test', 'entity_test', 'full')
->setComponent($field_name, array(
'type' => 'email_mailto',
))
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget found.');
$this->assertRaw('placeholder="example@example.com"');
// Submit a valid email address and ensure it is accepted.
$value = 'test@example.com';
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
$this->assertRaw($value);
// Verify that a mailto link is displayed.
$entity = entity_load('entity_test', $id);
$display = entity_get_display($entity->getEntityTypeId(), $entity->bundle(), 'full');
$content = $display->build($entity);
$this->setRawContent(\Drupal::service('renderer')->renderRoot($content));
$this->assertLinkByHref('mailto:test@example.com');
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Email\EmailItemTest.
*/
namespace Drupal\field\Tests\Email;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\field\Tests\FieldUnitTestBase;
/**
* Tests the new entity API for the email field type.
*
* @group field
*/
class EmailItemTest extends FieldUnitTestBase {
protected function setUp() {
parent::setUp();
// Create an email field storage and field for validation.
entity_create('field_storage_config', array(
'field_name' => 'field_email',
'entity_type' => 'entity_test',
'type' => 'email',
))->save();
entity_create('field_config', array(
'entity_type' => 'entity_test',
'field_name' => 'field_email',
'bundle' => 'entity_test',
))->save();
// Create a form display for the default form mode.
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent('field_email', array(
'type' => 'email_default',
))
->save();
}
/**
* Tests using entity fields of the email field type.
*/
public function testEmailItem() {
// Verify entity creation.
$entity = entity_create('entity_test');
$value = 'test@example.com';
$entity->field_email = $value;
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertTrue($entity->field_email instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_email[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_email->value, $value);
$this->assertEqual($entity->field_email[0]->value, $value);
// Verify changing the email value.
$new_value = $this->randomMachineName();
$entity->field_email->value = $new_value;
$this->assertEqual($entity->field_email->value, $new_value);
// Read changed entity and assert changed values.
$entity->save();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->field_email->value, $new_value);
// Test sample item generation.
$entity = entity_create('entity_test');
$entity->field_email->generateSampleItems();
$this->entityValidateAndSave($entity);
}
}

View file

@ -0,0 +1,312 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\EntityReference\EntityReferenceFormatterTest.
*/
namespace Drupal\field\Tests\EntityReference;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_reference\Tests\EntityReferenceTestTrait;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\filter\Entity\FilterFormat;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the formatters functionality.
*
* @group entity_reference
*/
class EntityReferenceFormatterTest extends EntityUnitTestBase {
use EntityReferenceTestTrait;
/**
* The entity type used in this test.
*
* @var string
*/
protected $entityType = 'entity_test';
/**
* The bundle used in this test.
*
* @var string
*/
protected $bundle = 'entity_test';
/**
* The name of the field used in this test.
*
* @var string
*/
protected $fieldName = 'field_test';
/**
* The entity to be referenced in this test.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $referencedEntity;
/**
* The entity that is not yet saved to its persistent storage to be referenced
* in this test.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $unsavedReferencedEntity;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('entity_reference');
protected function setUp() {
parent::setUp();
// Grant the 'view test entity' permission.
$this->installConfig(array('user'));
Role::load(RoleInterface::ANONYMOUS_ID)
->grantPermission('view test entity')
->save();
// The label formatter rendering generates links, so build the router.
$this->installSchema('system', 'router');
$this->container->get('router.builder')->rebuild();
$this->createEntityReferenceField($this->entityType, $this->bundle, $this->fieldName, 'Field test', $this->entityType, 'default', array(), FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
// Set up a field, so that the entity that'll be referenced bubbles up a
// cache tag when rendering it entirely.
entity_create('field_storage_config', array(
'field_name' => 'body',
'entity_type' => $this->entityType,
'type' => 'text',
'settings' => array(),
))->save();
entity_create('field_config', array(
'entity_type' => $this->entityType,
'bundle' => $this->bundle,
'field_name' => 'body',
'label' => 'Body',
))->save();
entity_get_display($this->entityType, $this->bundle, 'default')
->setComponent('body', array(
'type' => 'text_default',
'settings' => array(),
))
->save();
entity_create('filter_format', array(
'format' => 'full_html',
'name' => 'Full HTML',
))->save();
// Create the entity to be referenced.
$this->referencedEntity = entity_create($this->entityType, array('name' => $this->randomMachineName()));
$this->referencedEntity->body = array(
'value' => '<p>Hello, world!</p>',
'format' => 'full_html',
);
$this->referencedEntity->save();
// Create another entity to be referenced but do not save it.
$this->unsavedReferencedEntity = entity_create($this->entityType, array('name' => $this->randomMachineName()));
$this->unsavedReferencedEntity->body = array(
'value' => '<p>Hello, unsaved world!</p>',
'format' => 'full_html',
);
}
/**
* Assert inaccessible items don't change the data of the fields.
*/
public function testAccess() {
// Revoke the 'view test entity' permission for this test.
Role::load(RoleInterface::ANONYMOUS_ID)
->revokePermission('view test entity')
->save();
$field_name = $this->fieldName;
$referencing_entity = entity_create($this->entityType, array('name' => $this->randomMachineName()));
$referencing_entity->save();
$referencing_entity->{$field_name}->entity = $this->referencedEntity;
// Assert user doesn't have access to the entity.
$this->assertFalse($this->referencedEntity->access('view'), 'Current user does not have access to view the referenced entity.');
$formatter_manager = $this->container->get('plugin.manager.field.formatter');
// Get all the existing formatters.
foreach ($formatter_manager->getOptions('entity_reference') as $formatter => $name) {
// Set formatter type for the 'full' view mode.
entity_get_display($this->entityType, $this->bundle, 'default')
->setComponent($field_name, array(
'type' => $formatter,
))
->save();
// Invoke entity view.
entity_view($referencing_entity, 'default');
// Verify the un-accessible item still exists.
$this->assertEqual($referencing_entity->{$field_name}->target_id, $this->referencedEntity->id(), format_string('The un-accessible item still exists after @name formatter was executed.', array('@name' => $name)));
}
}
/**
* Tests the ID formatter.
*/
public function testIdFormatter() {
$formatter = 'entity_reference_entity_id';
$build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter);
$this->assertEqual($build[0]['#markup'], $this->referencedEntity->id(), sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
$this->assertEqual($build[0]['#cache']['tags'], $this->referencedEntity->getCacheTags(), sprintf('The %s formatter has the expected cache tags.', $formatter));
$this->assertTrue(!isset($build[1]), sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
}
/**
* Tests the entity formatter.
*/
public function testEntityFormatter() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$formatter = 'entity_reference_entity_view';
$build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter);
// Test the first field item.
$expected_rendered_name_field_1 = '<div class="field field-entity-test--name field-name-name field-type-string field-label-hidden">
<div class="field-items">
<div class="field-item">' . $this->referencedEntity->label() . '</div>
</div>
</div>
';
$expected_rendered_body_field_1 = '<div class="field field-entity-test--body field-name-body field-type-text field-label-above">
<div class="field-label">Body</div>
<div class="field-items">
<div class="field-item"><p>Hello, world!</p></div>
</div>
</div>
';
$renderer->renderRoot($build[0]);
$this->assertEqual($build[0]['#markup'], 'default | ' . $this->referencedEntity->label() . $expected_rendered_name_field_1 . $expected_rendered_body_field_1, sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
$expected_cache_tags = Cache::mergeTags(
\Drupal::entityManager()->getViewBuilder($this->entityType)->getCacheTags(),
$this->referencedEntity->getCacheTags(),
FilterFormat::load('full_html')->getCacheTags()
);
$this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags.', array('@formatter' => $formatter)));
// Test the second field item.
$renderer->renderRoot($build[1]);
$this->assertEqual($build[1]['#markup'], $this->unsavedReferencedEntity->label(), sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
}
/**
* Tests the label formatter.
*/
public function testLabelFormatter() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$formatter = 'entity_reference_label';
// The 'link' settings is TRUE by default.
$build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter);
$expected_field_cacheability = [
'contexts' => [],
'tags' => [],
'max-age' => Cache::PERMANENT,
];
$this->assertEqual($build['#cache'], $expected_field_cacheability, 'The field render array contains the entity access cacheability metadata');
$expected_item_1 = array(
'#type' => 'link',
'#title' => $this->referencedEntity->label(),
'#url' => $this->referencedEntity->urlInfo(),
'#options' => $this->referencedEntity->urlInfo()->getOptions(),
'#cache' => array(
'contexts' => [
'user.permissions',
],
'tags' => $this->referencedEntity->getCacheTags(),
),
);
$this->assertEqual($renderer->renderRoot($build[0]), $renderer->renderRoot($expected_item_1), sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
$this->assertEqual(CacheableMetadata::createFromRenderArray($build[0]), CacheableMetadata::createFromRenderArray($expected_item_1));
// The second referenced entity is "autocreated", therefore not saved and
// lacking any URL info.
$expected_item_2 = array(
'#markup' => $this->unsavedReferencedEntity->label(),
'#cache' => array(
'contexts' => [
'user.permissions',
],
'tags' => $this->unsavedReferencedEntity->getCacheTags(),
'max-age' => Cache::PERMANENT,
),
);
$this->assertEqual($build[1], $expected_item_2, sprintf('The render array returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
// Test with the 'link' setting set to FALSE.
$build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter, array('link' => FALSE));
$this->assertEqual($build[0]['#markup'], $this->referencedEntity->label(), sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
$this->assertEqual($build[1]['#markup'], $this->unsavedReferencedEntity->label(), sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
// Test an entity type that doesn't have any link templates, which means
// \Drupal\Core\Entity\EntityInterface::urlInfo() will throw an exception
// and the label formatter will output only the label instead of a link.
$field_storage_config = FieldStorageConfig::loadByName($this->entityType, $this->fieldName);
$field_storage_config->setSetting('target_type', 'entity_test_label');
$field_storage_config->save();
$referenced_entity_with_no_link_template = entity_create('entity_test_label', array(
'name' => $this->randomMachineName(),
));
$referenced_entity_with_no_link_template->save();
$build = $this->buildRenderArray([$referenced_entity_with_no_link_template], $formatter, array('link' => TRUE));
$this->assertEqual($build[0]['#markup'], $referenced_entity_with_no_link_template->label(), sprintf('The markup returned by the %s formatter is correct for an entity type with no valid link template.', $formatter));
}
/**
* Sets field values and returns a render array as built by
* \Drupal\Core\Field\FieldItemListInterface::view().
*
* @param \Drupal\Core\Entity\EntityInterface[] $referenced_entities
* An array of entity objects that will be referenced.
* @param string $formatter
* The formatted plugin that will be used for building the render array.
* @param array $formatter_options
* Settings specific to the formatter. Defaults to the formatter's default
* settings.
*
* @return array
* A render array.
*/
protected function buildRenderArray(array $referenced_entities, $formatter, $formatter_options = array()) {
// Create the entity that will have the entity reference field.
$referencing_entity = entity_create($this->entityType, array('name' => $this->randomMachineName()));
$items = $referencing_entity->get($this->fieldName);
// Assign the referenced entities.
foreach ($referenced_entities as $referenced_entity) {
$items[] = ['entity' => $referenced_entity];
}
// Build the renderable array for the field.
return $items->view(array('type' => $formatter, 'settings' => $formatter_options));
}
}

View file

@ -0,0 +1,283 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\EntityReference\EntityReferenceItemTest.
*/
namespace Drupal\field\Tests\EntityReference;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_reference\Tests\EntityReferenceTestTrait;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\entity_test\Entity\EntityTestStringId;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Tests\FieldUnitTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the new entity API for the entity reference field type.
*
* @group entity_reference
*/
class EntityReferenceItemTest extends FieldUnitTestBase {
use EntityReferenceTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('entity_reference', 'taxonomy', 'text', 'filter', 'views');
/**
* The taxonomy vocabulary to test with.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* The taxonomy term to test with.
*
* @var \Drupal\taxonomy\TermInterface
*/
protected $term;
/**
* The test entity with a string ID.
*
* @var \Drupal\entity_test\Entity\EntityTestStringId
*/
protected $entityStringId;
/**
* Sets up the test.
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_string_id');
$this->installEntitySchema('taxonomy_term');
$this->vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => $this->randomMachineName(),
'vid' => Unicode::strtolower($this->randomMachineName()),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->vocabulary->save();
$this->term = entity_create('taxonomy_term', array(
'name' => $this->randomMachineName(),
'vid' => $this->vocabulary->id(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->term->save();
$this->entityStringId = EntityTestStringId::create([
'id' => $this->randomMachineName(),
]);
$this->entityStringId->save();
// Use the util to create an instance.
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_term', 'Test content entity reference', 'taxonomy_term');
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_entity_test_string_id', 'Test content entity reference with string ID', 'entity_test_string_id');
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_vocabulary', 'Test config entity reference', 'taxonomy_vocabulary');
}
/**
* Tests the entity reference field type for referencing content entities.
*/
public function testContentEntityReferenceItem() {
$tid = $this->term->id();
// Just being able to create the entity like this verifies a lot of code.
$entity = entity_create('entity_test');
$entity->field_test_taxonomy_term->target_id = $tid;
$entity->name->value = $this->randomMachineName();
$entity->save();
$entity = entity_load('entity_test', $entity->id());
$this->assertTrue($entity->field_test_taxonomy_term instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_test_taxonomy_term[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_test_taxonomy_term->target_id, $tid);
$this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $this->term->getName());
$this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $tid);
$this->assertEqual($entity->field_test_taxonomy_term->entity->uuid(), $this->term->uuid());
// Change the name of the term via the reference.
$new_name = $this->randomMachineName();
$entity->field_test_taxonomy_term->entity->setName($new_name);
$entity->field_test_taxonomy_term->entity->save();
// Verify it is the correct name.
$term = Term::load($tid);
$this->assertEqual($term->getName(), $new_name);
// Make sure the computed term reflects updates to the term id.
$term2 = entity_create('taxonomy_term', array(
'name' => $this->randomMachineName(),
'vid' => $this->term->bundle(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$term2->save();
// Test all the possible ways of assigning a value.
$entity->field_test_taxonomy_term->target_id = $term->id();
$this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
$this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term->getName());
$entity->field_test_taxonomy_term = [['target_id' => $term2->id()]];
$this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term2->id());
$this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term2->getName());
// Test value assignment via the computed 'entity' property.
$entity->field_test_taxonomy_term->entity = $term;
$this->assertEqual($entity->field_test_taxonomy_term->target_id, $term->id());
$this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term->getName());
$entity->field_test_taxonomy_term = [['entity' => $term2]];
$this->assertEqual($entity->field_test_taxonomy_term->target_id, $term2->id());
$this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term2->getName());
// Test assigning an invalid item throws an exception.
try {
$entity->field_test_taxonomy_term = ['target_id' => 'invalid', 'entity' => $term2];
$this->fail('Assigning an invalid item throws an exception.');
}
catch (\InvalidArgumentException $e) {
$this->pass('Assigning an invalid item throws an exception.');
}
// Delete terms so we have nothing to reference and try again
$term->delete();
$term2->delete();
$entity = entity_create('entity_test', array('name' => $this->randomMachineName()));
$entity->save();
// Test the generateSampleValue() method.
$entity = entity_create('entity_test');
$entity->field_test_taxonomy_term->generateSampleItems();
$entity->field_test_taxonomy_vocabulary->generateSampleItems();
$this->entityValidateAndSave($entity);
}
/**
* Tests referencing content entities with string IDs.
*/
public function testContentEntityReferenceItemWithStringId() {
$entity = EntityTest::create();
$entity->field_test_entity_test_string_id->target_id = $this->entityStringId->id();
$entity->save();
$storage = \Drupal::entityManager()->getStorage('entity_test');
$storage->resetCache();
$this->assertEqual($this->entityStringId->id(), $storage->load($entity->id())->field_test_entity_test_string_id->target_id);
}
/**
* Tests the entity reference field type for referencing config entities.
*/
public function testConfigEntityReferenceItem() {
$referenced_entity_id = $this->vocabulary->id();
// Just being able to create the entity like this verifies a lot of code.
$entity = entity_create('entity_test');
$entity->field_test_taxonomy_vocabulary->target_id = $referenced_entity_id;
$entity->name->value = $this->randomMachineName();
$entity->save();
$entity = entity_load('entity_test', $entity->id());
$this->assertTrue($entity->field_test_taxonomy_vocabulary instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_test_taxonomy_vocabulary[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_test_taxonomy_vocabulary->target_id, $referenced_entity_id);
$this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->label(), $this->vocabulary->label());
$this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->id(), $referenced_entity_id);
$this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->uuid(), $this->vocabulary->uuid());
// Change the name of the term via the reference.
$new_name = $this->randomMachineName();
$entity->field_test_taxonomy_vocabulary->entity->set('name', $new_name);
$entity->field_test_taxonomy_vocabulary->entity->save();
// Verify it is the correct name.
$vocabulary = Vocabulary::load($referenced_entity_id);
$this->assertEqual($vocabulary->label(), $new_name);
// Make sure the computed term reflects updates to the term id.
$vocabulary2 = entity_create('taxonomy_vocabulary', array(
'name' => $this->randomMachineName(),
'vid' => Unicode::strtolower($this->randomMachineName()),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$vocabulary2->save();
$entity->field_test_taxonomy_vocabulary->target_id = $vocabulary2->id();
$this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->id(), $vocabulary2->id());
$this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->label(), $vocabulary2->label());
// Delete terms so we have nothing to reference and try again
$this->vocabulary->delete();
$vocabulary2->delete();
$entity = entity_create('entity_test', array('name' => $this->randomMachineName()));
$entity->save();
}
/**
* Test saving order sequence doesn't matter.
*/
public function testEntitySaveOrder() {
// The term entity is unsaved here.
$term = entity_create('taxonomy_term', array(
'name' => $this->randomMachineName(),
'vid' => $this->term->bundle(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$entity = entity_create('entity_test');
// Now assign the unsaved term to the field.
$entity->field_test_taxonomy_term->entity = $term;
$entity->name->value = $this->randomMachineName();
// Now save the term.
$term->save();
// And then the entity.
$entity->save();
$this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
}
/**
* Tests that the 'handler' field setting stores the proper plugin ID.
*/
public function testSelectionHandlerSettings() {
$field_name = Unicode::strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'entity_reference',
'settings' => array(
'target_type' => 'entity_test'
),
));
$field_storage->save();
// Do not specify any value for the 'handler' setting in order to verify
// that the default value is properly used.
$field = FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => 'entity_test',
));
$field->save();
$field = FieldConfig::load($field->id());
$this->assertTrue($field->getSetting('handler') == 'default:entity_test');
$field->setSetting('handler', 'views');
$field->save();
$field = FieldConfig::load($field->id());
$this->assertTrue($field->getSetting('handler') == 'views');
}
}

View file

@ -0,0 +1,93 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldAccessTest.
*/
namespace Drupal\field\Tests;
/**
* Tests Field access.
*
* @group field
*/
class FieldAccessTest extends FieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'field_test');
/**
* Node entity to use in this test.
*
* @var \Drupal\node\Entity\Node
*/
protected $node;
/**
* Field value to test display on nodes.
*
* @var string
*/
protected $testViewFieldValue;
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('view test_view_field content'));
$this->drupalLogin($web_user);
// Create content type.
$content_type_info = $this->drupalCreateContentType();
$content_type = $content_type_info->id();
$field_storage = array(
'field_name' => 'test_view_field',
'entity_type' => 'node',
'type' => 'text',
);
entity_create('field_storage_config', $field_storage)->save();
$field = array(
'field_name' => $field_storage['field_name'],
'entity_type' => 'node',
'bundle' => $content_type,
);
entity_create('field_config', $field)->save();
// Assign display properties for the 'default' and 'teaser' view modes.
foreach (array('default', 'teaser') as $view_mode) {
entity_get_display('node', $content_type, $view_mode)
->setComponent($field_storage['field_name'])
->save();
}
// Create test node.
$this->testViewFieldValue = 'This is some text';
$settings = array();
$settings['type'] = $content_type;
$settings['title'] = 'Field view access test';
$settings['test_view_field'] = array(array('value' => $this->testViewFieldValue));
$this->node = $this->drupalCreateNode($settings);
}
/**
* Test that hook_entity_field_access() is called.
*/
function testFieldAccess() {
// Assert the text is visible.
$this->drupalGet('node/' . $this->node->id());
$this->assertText($this->testViewFieldValue);
// Assert the text is not visible for anonymous users.
// The field_test module implements hook_entity_field_access() which will
// specifically target the 'test_view_field' field.
$this->drupalLogout();
$this->drupalGet('node/' . $this->node->id());
$this->assertNoText($this->testViewFieldValue);
}
}

View file

@ -0,0 +1,380 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldAttachOtherTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Form\FormState;
use Drupal\Core\Language\LanguageInterface;
/**
* Tests other Field API functions.
*
* @group field
* @todo move this to the Entity module
*/
class FieldAttachOtherTest extends FieldUnitTestBase {
protected function setUp() {
parent::setUp();
$this->container->get('router.builder')->rebuild();
$this->installEntitySchema('entity_test_rev');
$this->createFieldWithStorage();
}
/**
* Test rendering fields with EntityDisplay build().
*/
function testEntityDisplayBuild() {
$this->createFieldWithStorage('_2');
$entity_type = 'entity_test';
$entity_init = entity_create($entity_type);
// Populate values to be displayed.
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage->getCardinality());
$entity_init->{$this->fieldTestData->field_name}->setValue($values);
$values_2 = $this->_generateTestFieldValues($this->fieldTestData->field_storage_2->getCardinality());
$entity_init->{$this->fieldTestData->field_name_2}->setValue($values_2);
// Simple formatter, label displayed.
$entity = clone($entity_init);
$display = entity_get_display($entity_type, $entity->bundle(), 'full');
$formatter_setting = $this->randomMachineName();
$display_options = array(
'label' => 'above',
'type' => 'field_test_default',
'settings' => array(
'test_formatter_setting' => $formatter_setting,
),
);
$display->setComponent($this->fieldTestData->field_name, $display_options);
$formatter_setting_2 = $this->randomMachineName();
$display_options_2 = array(
'label' => 'above',
'type' => 'field_test_default',
'settings' => array(
'test_formatter_setting' => $formatter_setting_2,
),
);
$display->setComponent($this->fieldTestData->field_name_2, $display_options_2);
// View all fields.
$content = $display->build($entity);
$this->render($content);
$this->assertRaw($this->fieldTestData->field->getLabel(), "First field's label is displayed.");
foreach ($values as $delta => $value) {
$this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
}
$this->assertRaw($this->fieldTestData->field_2->getLabel(), "Second field's label is displayed.");
foreach ($values_2 as $delta => $value) {
$this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
}
// Label hidden.
$entity = clone($entity_init);
$display_options['label'] = 'hidden';
$display->setComponent($this->fieldTestData->field_name, $display_options);
$content = $display->build($entity);
$this->render($content);
$this->assertNoRaw($this->fieldTestData->field->getLabel(), "Hidden label: label is not displayed.");
// Field hidden.
$entity = clone($entity_init);
$display->removeComponent($this->fieldTestData->field_name);
$content = $display->build($entity);
$this->render($content);
$this->assertNoRaw($this->fieldTestData->field->getLabel(), "Hidden field: label is not displayed.");
foreach ($values as $delta => $value) {
$this->assertNoRaw("$formatter_setting|{$value['value']}", "Hidden field: value $delta is not displayed.");
}
// Multiple formatter.
$entity = clone($entity_init);
$formatter_setting = $this->randomMachineName();
$display->setComponent($this->fieldTestData->field_name, array(
'label' => 'above',
'type' => 'field_test_multiple',
'settings' => array(
'test_formatter_setting_multiple' => $formatter_setting,
),
));
$content = $display->build($entity);
$this->render($content);
$expected_output = $formatter_setting;
foreach ($values as $delta => $value) {
$expected_output .= "|$delta:{$value['value']}";
}
$this->assertRaw($expected_output, "Multiple formatter: all values are displayed, formatter settings are applied.");
// Test a formatter that uses hook_field_formatter_prepare_view().
$entity = clone($entity_init);
$formatter_setting = $this->randomMachineName();
$display->setComponent($this->fieldTestData->field_name, array(
'label' => 'above',
'type' => 'field_test_with_prepare_view',
'settings' => array(
'test_formatter_setting_additional' => $formatter_setting,
),
));
$content = $display->build($entity);
$this->render($content);
foreach ($values as $delta => $value) {
$expected = $formatter_setting . '|' . $value['value'] . '|' . ($value['value'] + 1);
$this->assertRaw($expected, "Value $delta is displayed, formatter settings are applied.");
}
// TODO:
// - check display order with several fields
}
/**
* Tests rendering fields with EntityDisplay::buildMultiple().
*/
function testEntityDisplayViewMultiple() {
// Use a formatter that has a prepareView() step.
$display = entity_get_display('entity_test', 'entity_test', 'full')
->setComponent($this->fieldTestData->field_name, array(
'type' => 'field_test_with_prepare_view',
));
// Create two entities.
$entity1 = entity_create('entity_test', array('id' => 1, 'type' => 'entity_test'));
$entity1->{$this->fieldTestData->field_name}->setValue($this->_generateTestFieldValues(1));
$entity2 = entity_create('entity_test', array('id' => 2, 'type' => 'entity_test'));
$entity2->{$this->fieldTestData->field_name}->setValue($this->_generateTestFieldValues(1));
// Run buildMultiple(), and check that the entities come out as expected.
$display->buildMultiple(array($entity1, $entity2));
$item1 = $entity1->{$this->fieldTestData->field_name}[0];
$this->assertEqual($item1->additional_formatter_value, $item1->value + 1, 'Entity 1 ran through the prepareView() formatter method.');
$item2 = $entity2->{$this->fieldTestData->field_name}[0];
$this->assertEqual($item2->additional_formatter_value, $item2->value + 1, 'Entity 2 ran through the prepareView() formatter method.');
}
/**
* Test entity cache.
*
* Complements unit test coverage in
* \Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTest.
*/
function testEntityCache() {
// Initialize random values and a test entity.
$entity_init = entity_create('entity_test', array('type' => $this->fieldTestData->field->getTargetBundle()));
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage->getCardinality());
// Non-cacheable entity type.
$entity_type = 'entity_test';
$cid = "values:$entity_type:" . $entity_init->id();
// Check that no initial cache entry is present.
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Non-cached: no initial cache entry');
// Save, and check that no cache entry is present.
$entity = clone($entity_init);
$entity->{$this->fieldTestData->field_name}->setValue($values);
$entity = $this->entitySaveReload($entity);
$cid = "values:$entity_type:" . $entity->id();
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Non-cached: no cache entry on insert and load');
// Cacheable entity type.
$entity_type = 'entity_test_rev';
$this->createFieldWithStorage('_2', $entity_type);
$entity_init = entity_create($entity_type, array(
'type' => $entity_type,
));
// Check that no initial cache entry is present.
$cid = "values:$entity_type:" . $entity->id();
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no initial cache entry');
// Save, and check that no cache entry is present.
$entity = clone($entity_init);
$entity->{$this->fieldTestData->field_name_2} = $values;
$entity->save();
$cid = "values:$entity_type:" . $entity->id();
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on insert');
// Load, and check that a cache entry is present with the expected values.
$controller = $this->container->get('entity.manager')->getStorage($entity->getEntityTypeId());
$controller->resetCache();
$cached_entity = $controller->load($entity->id());
$cache = \Drupal::cache('entity')->get($cid);
$this->assertEqual($cache->data, $cached_entity, 'Cached: correct cache entry on load');
// Update with different values, and check that the cache entry is wiped.
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage_2->getCardinality());
$entity->{$this->fieldTestData->field_name_2} = $values;
$entity->save();
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on update');
// Load, and check that a cache entry is present with the expected values.
$controller->resetCache();
$cached_entity = $controller->load($entity->id());
$cache = \Drupal::cache('entity')->get($cid);
$this->assertEqual($cache->data, $cached_entity, 'Cached: correct cache entry on load');
// Create a new revision, and check that the cache entry is wiped.
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage_2->getCardinality());
$entity->{$this->fieldTestData->field_name_2} = $values;
$entity->setNewRevision();
$entity->save();
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on new revision creation');
// Load, and check that a cache entry is present with the expected values.
$controller->resetCache();
$cached_entity = $controller->load($entity->id());
$cache = \Drupal::cache('entity')->get($cid);
$this->assertEqual($cache->data, $cached_entity, 'Cached: correct cache entry on load');
// Delete, and check that the cache entry is wiped.
$entity->delete();
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry after delete');
}
/**
* Tests \Drupal\Core\Entity\Display\EntityFormDisplayInterface::buildForm().
*
* This could be much more thorough, but it does verify that the correct
* widgets show up.
*/
function testEntityFormDisplayBuildForm() {
$this->createFieldWithStorage('_2');
$entity_type = 'entity_test';
$entity = entity_create($entity_type, array('id' => 1, 'revision_id' => 1, 'type' => $this->fieldTestData->field->getTargetBundle()));
// Test generating widgets for all fields.
$display = entity_get_form_display($entity_type, $this->fieldTestData->field->getTargetBundle(), 'default');
$form = array();
$form_state = new FormState();
$display->buildForm($entity, $form, $form_state);
$this->assertEqual($form[$this->fieldTestData->field_name]['widget']['#title'], $this->fieldTestData->field->getLabel(), "First field's form title is {$this->fieldTestData->field->getLabel()}");
$this->assertEqual($form[$this->fieldTestData->field_name_2]['widget']['#title'], $this->fieldTestData->field_2->getLabel(), "Second field's form title is {$this->fieldTestData->field_2->getLabel()}");
for ($delta = 0; $delta < $this->fieldTestData->field_storage->getCardinality(); $delta++) {
// field_test_widget uses 'textfield'
$this->assertEqual($form[$this->fieldTestData->field_name]['widget'][$delta]['value']['#type'], 'textfield', "First field's form delta $delta widget is textfield");
}
for ($delta = 0; $delta < $this->fieldTestData->field_storage_2->getCardinality(); $delta++) {
// field_test_widget uses 'textfield'
$this->assertEqual($form[$this->fieldTestData->field_name_2]['widget'][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield");
}
// Test generating widgets for all fields.
$display = entity_get_form_display($entity_type, $this->fieldTestData->field->getTargetBundle(), 'default');
foreach ($display->getComponents() as $name => $options) {
if ($name != $this->fieldTestData->field_name_2) {
$display->removeComponent($name);
}
}
$form = array();
$form_state = new FormState();
$display->buildForm($entity, $form, $form_state);
$this->assertFalse(isset($form[$this->fieldTestData->field_name]), 'The first field does not exist in the form');
$this->assertEqual($form[$this->fieldTestData->field_name_2]['widget']['#title'], $this->fieldTestData->field_2->getLabel(), "Second field's form title is {$this->fieldTestData->field_2->getLabel()}");
for ($delta = 0; $delta < $this->fieldTestData->field_storage_2->getCardinality(); $delta++) {
// field_test_widget uses 'textfield'
$this->assertEqual($form[$this->fieldTestData->field_name_2]['widget'][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield");
}
}
/**
* Tests \Drupal\Core\Entity\Display\EntityFormDisplayInterface::extractFormValues().
*/
function testEntityFormDisplayExtractFormValues() {
$this->createFieldWithStorage('_2');
$entity_type = 'entity_test';
$entity_init = entity_create($entity_type, array('id' => 1, 'revision_id' => 1, 'type' => $this->fieldTestData->field->getTargetBundle()));
// Build the form for all fields.
$display = entity_get_form_display($entity_type, $this->fieldTestData->field->getTargetBundle(), 'default');
$form = array();
$form_state = new FormState();
$display->buildForm($entity_init, $form, $form_state);
// Simulate incoming values.
// First field.
$values = array();
$weights = array();
for ($delta = 0; $delta < $this->fieldTestData->field_storage->getCardinality(); $delta++) {
$values[$delta]['value'] = mt_rand(1, 127);
// Assign random weight.
do {
$weight = mt_rand(0, $this->fieldTestData->field_storage->getCardinality());
} while (in_array($weight, $weights));
$weights[$delta] = $weight;
$values[$delta]['_weight'] = $weight;
}
// Leave an empty value. 'field_test' fields are empty if empty().
$values[1]['value'] = 0;
// Second field.
$values_2 = array();
$weights_2 = array();
for ($delta = 0; $delta < $this->fieldTestData->field_storage_2->getCardinality(); $delta++) {
$values_2[$delta]['value'] = mt_rand(1, 127);
// Assign random weight.
do {
$weight = mt_rand(0, $this->fieldTestData->field_storage_2->getCardinality());
} while (in_array($weight, $weights_2));
$weights_2[$delta] = $weight;
$values_2[$delta]['_weight'] = $weight;
}
// Leave an empty value. 'field_test' fields are empty if empty().
$values_2[1]['value'] = 0;
// Pretend the form has been built.
$form_state->setFormObject(\Drupal::entityManager()->getFormObject($entity_type, 'default'));
\Drupal::formBuilder()->prepareForm('field_test_entity_form', $form, $form_state);
\Drupal::formBuilder()->processForm('field_test_entity_form', $form, $form_state);
$form_state->setValue($this->fieldTestData->field_name, $values);
$form_state->setValue($this->fieldTestData->field_name_2, $values_2);
// Extract values for all fields.
$entity = clone($entity_init);
$display->extractFormValues($entity, $form, $form_state);
asort($weights);
asort($weights_2);
$expected_values = array();
$expected_values_2 = array();
foreach ($weights as $key => $value) {
if ($key != 1) {
$expected_values[] = array('value' => $values[$key]['value']);
}
}
$this->assertIdentical($entity->{$this->fieldTestData->field_name}->getValue(), $expected_values, 'Submit filters empty values');
foreach ($weights_2 as $key => $value) {
if ($key != 1) {
$expected_values_2[] = array('value' => $values_2[$key]['value']);
}
}
$this->assertIdentical($entity->{$this->fieldTestData->field_name_2}->getValue(), $expected_values_2, 'Submit filters empty values');
// Call EntityFormDisplayInterface::extractFormValues() for a single field (the second field).
foreach ($display->getComponents() as $name => $options) {
if ($name != $this->fieldTestData->field_name_2) {
$display->removeComponent($name);
}
}
$entity = clone($entity_init);
$display->extractFormValues($entity, $form, $form_state);
$expected_values_2 = array();
foreach ($weights_2 as $key => $value) {
if ($key != 1) {
$expected_values_2[] = array('value' => $values_2[$key]['value']);
}
}
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'The first field is empty in the entity object');
$this->assertIdentical($entity->{$this->fieldTestData->field_name_2}->getValue(), $expected_values_2, 'Submit filters empty values');
}
}

View file

@ -0,0 +1,378 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldAttachStorageTest.
*/
namespace Drupal\field\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\field\Entity\FieldConfig;
/**
* Tests storage-related Field Attach API functions.
*
* @group field
* @todo move this to the Entity module
*/
class FieldAttachStorageTest extends FieldUnitTestBase {
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_rev');
}
/**
* Check field values insert, update and load.
*
* Works independently of the underlying field storage backend. Inserts or
* updates random field data and then loads and verifies the data.
*/
function testFieldAttachSaveLoad() {
$entity_type = 'entity_test_rev';
$this->createFieldWithStorage('', $entity_type);
$cardinality = $this->fieldTestData->field_storage->getCardinality();
// TODO : test empty values filtering and "compression" (store consecutive deltas).
// Preparation: create three revisions and store them in $revision array.
$values = array();
$entity = entity_create($entity_type);
for ($revision_id = 0; $revision_id < 3; $revision_id++) {
// Note: we try to insert one extra value.
$current_values = $this->_generateTestFieldValues($cardinality + 1);
$entity->{$this->fieldTestData->field_name}->setValue($current_values);
$entity->setNewRevision();
$entity->save();
$entity_id = $entity->id();
$current_revision = $entity->getRevisionId();
$values[$current_revision] = $current_values;
}
$storage = $this->container->get('entity.manager')->getStorage($entity_type);
$storage->resetCache();
$entity = $storage->load($entity_id);
// Confirm current revision loads the correct data.
// Number of values per field loaded equals the field cardinality.
$this->assertEqual(count($entity->{$this->fieldTestData->field_name}), $cardinality, 'Current revision: expected number of values');
for ($delta = 0; $delta < $cardinality; $delta++) {
// The field value loaded matches the one inserted or updated.
$this->assertEqual($entity->{$this->fieldTestData->field_name}[$delta]->value , $values[$current_revision][$delta]['value'], format_string('Current revision: expected value %delta was found.', array('%delta' => $delta)));
}
// Confirm each revision loads the correct data.
foreach (array_keys($values) as $revision_id) {
$entity = $storage->loadRevision($revision_id);
// Number of values per field loaded equals the field cardinality.
$this->assertEqual(count($entity->{$this->fieldTestData->field_name}), $cardinality, format_string('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
for ($delta = 0; $delta < $cardinality; $delta++) {
// The field value loaded matches the one inserted or updated.
$this->assertEqual($entity->{$this->fieldTestData->field_name}[$delta]->value, $values[$revision_id][$delta]['value'], format_string('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta)));
}
}
}
/**
* Test the 'multiple' load feature.
*/
function testFieldAttachLoadMultiple() {
$entity_type = 'entity_test_rev';
// Define 2 bundles.
$bundles = array(
1 => 'test_bundle_1',
2 => 'test_bundle_2',
);
entity_test_create_bundle($bundles[1]);
entity_test_create_bundle($bundles[2]);
// Define 3 fields:
// - field_1 is in bundle_1 and bundle_2,
// - field_2 is in bundle_1,
// - field_3 is in bundle_2.
$field_bundles_map = array(
1 => array(1, 2),
2 => array(1),
3 => array(2),
);
for ($i = 1; $i <= 3; $i++) {
$field_names[$i] = 'field_' . $i;
$field_storage = entity_create('field_storage_config', array(
'field_name' => $field_names[$i],
'entity_type' => $entity_type,
'type' => 'test_field',
));
$field_storage->save();
$field_ids[$i] = $field_storage->uuid();
foreach ($field_bundles_map[$i] as $bundle) {
entity_create('field_config', array(
'field_name' => $field_names[$i],
'entity_type' => $entity_type,
'bundle' => $bundles[$bundle],
))->save();
}
}
// Create one test entity per bundle, with random values.
foreach ($bundles as $index => $bundle) {
$entities[$index] = entity_create($entity_type, array('id' => $index, 'revision_id' => $index, 'type' => $bundle));
$entity = clone($entities[$index]);
foreach ($field_names as $field_name) {
if (!$entity->hasField($field_name)) {
continue;
}
$values[$index][$field_name] = mt_rand(1, 127);
$entity->$field_name->setValue(array('value' => $values[$index][$field_name]));
}
$entity->enforceIsnew();
$entity->save();
}
// Check that a single load correctly loads field values for both entities.
$controller = \Drupal::entityManager()->getStorage($entity->getEntityTypeId());
$controller->resetCache();
$entities = $controller->loadMultiple();
foreach ($entities as $index => $entity) {
foreach ($field_names as $field_name) {
if (!$entity->hasField($field_name)) {
continue;
}
// The field value loaded matches the one inserted.
$this->assertEqual($entity->{$field_name}->value, $values[$index][$field_name], format_string('Entity %index: expected value was found.', array('%index' => $index)));
}
}
}
/**
* Tests insert and update with empty or NULL fields.
*/
function testFieldAttachSaveEmptyData() {
$entity_type = 'entity_test';
$this->createFieldWithStorage('', $entity_type);
$entity_init = entity_create($entity_type, array('id' => 1));
// Insert: Field is NULL.
$entity = clone $entity_init;
$entity->{$this->fieldTestData->field_name} = NULL;
$entity->enforceIsNew();
$entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'Insert: NULL field results in no value saved');
// All saves after this point should be updates, not inserts.
$entity_init->enforceIsNew(FALSE);
// Add some real data.
$entity = clone($entity_init);
$values = $this->_generateTestFieldValues(1);
$entity->{$this->fieldTestData->field_name} = $values;
$entity = $this->entitySaveReload($entity);
$this->assertEqual($entity->{$this->fieldTestData->field_name}->getValue(), $values, 'Field data saved');
// Update: Field is NULL. Data should be wiped.
$entity = clone($entity_init);
$entity->{$this->fieldTestData->field_name} = NULL;
$entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'Update: NULL field removes existing values');
// Re-add some data.
$entity = clone($entity_init);
$values = $this->_generateTestFieldValues(1);
$entity->{$this->fieldTestData->field_name} = $values;
$entity = $this->entitySaveReload($entity);
$this->assertEqual($entity->{$this->fieldTestData->field_name}->getValue(), $values, 'Field data saved');
// Update: Field is empty array. Data should be wiped.
$entity = clone($entity_init);
$entity->{$this->fieldTestData->field_name} = array();
$entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'Update: empty array removes existing values');
}
/**
* Test insert with empty or NULL fields, with default value.
*/
function testFieldAttachSaveEmptyDataDefaultValue() {
$entity_type = 'entity_test_rev';
$this->createFieldWithStorage('', $entity_type);
// Add a default value function.
$this->fieldTestData->field->set('default_value_callback', 'field_test_default_value');
$this->fieldTestData->field->save();
// Verify that fields are populated with default values.
$entity_init = entity_create($entity_type, array('id' => 1, 'revision_id' => 1));
$default = field_test_default_value($entity_init, $this->fieldTestData->field);
$this->assertEqual($entity_init->{$this->fieldTestData->field_name}->getValue(), $default, 'Default field value correctly populated.');
// Insert: Field is NULL.
$entity = clone($entity_init);
$entity->{$this->fieldTestData->field_name} = NULL;
$entity->enforceIsNew();
$entity = $this->entitySaveReload($entity);
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'Insert: NULL field results in no value saved');
// Verify that prepopulated field values are not overwritten by defaults.
$value = array(array('value' => $default[0]['value'] - mt_rand(1, 127)));
$entity = entity_create($entity_type, array('type' => $entity_init->bundle(), $this->fieldTestData->field_name => $value));
$this->assertEqual($entity->{$this->fieldTestData->field_name}->getValue(), $value, 'Prepopulated field value correctly maintained.');
}
/**
* Test entity deletion.
*/
function testFieldAttachDelete() {
$entity_type = 'entity_test_rev';
$this->createFieldWithStorage('', $entity_type);
$cardinality = $this->fieldTestData->field_storage->getCardinality();
$entity = entity_create($entity_type, array('type' => $this->fieldTestData->field->getTargetBundle()));
$vids = array();
// Create revision 0
$values = $this->_generateTestFieldValues($cardinality);
$entity->{$this->fieldTestData->field_name} = $values;
$entity->save();
$vids[] = $entity->getRevisionId();
// Create revision 1
$entity->setNewRevision();
$entity->save();
$vids[] = $entity->getRevisionId();
// Create revision 2
$entity->setNewRevision();
$entity->save();
$vids[] = $entity->getRevisionId();
$controller = $this->container->get('entity.manager')->getStorage($entity->getEntityTypeId());
$controller->resetCache();
// Confirm each revision loads
foreach ($vids as $vid) {
$revision = $controller->loadRevision($vid);
$this->assertEqual(count($revision->{$this->fieldTestData->field_name}), $cardinality, "The test entity revision $vid has $cardinality values.");
}
// Delete revision 1, confirm the other two still load.
$controller->deleteRevision($vids[1]);
$controller->resetCache();
foreach (array(0, 2) as $key) {
$vid = $vids[$key];
$revision = $controller->loadRevision($vid);
$this->assertEqual(count($revision->{$this->fieldTestData->field_name}), $cardinality, "The test entity revision $vid has $cardinality values.");
}
// Confirm the current revision still loads
$controller->resetCache();
$current = $controller->load($entity->id());
$this->assertEqual(count($current->{$this->fieldTestData->field_name}), $cardinality, "The test entity current revision has $cardinality values.");
// Delete all field data, confirm nothing loads
$entity->delete();
$controller->resetCache();
foreach (array(0, 1, 2) as $vid) {
$revision = $controller->loadRevision($vid);
$this->assertFalse($revision);
}
$this->assertFalse($controller->load($entity->id()));
}
/**
* Test entity_bundle_create() and entity_bundle_rename().
*/
function testEntityCreateRenameBundle() {
$entity_type = 'entity_test_rev';
$this->createFieldWithStorage('', $entity_type);
$cardinality = $this->fieldTestData->field_storage->getCardinality();
// Create a new bundle.
$new_bundle = 'test_bundle_' . Unicode::strtolower($this->randomMachineName());
entity_test_create_bundle($new_bundle, NULL, $entity_type);
// Add a field to that bundle.
$this->fieldTestData->field_definition['bundle'] = $new_bundle;
entity_create('field_config', $this->fieldTestData->field_definition)->save();
// Save an entity with data in the field.
$entity = entity_create($entity_type, array('type' => $this->fieldTestData->field->getTargetBundle()));
$values = $this->_generateTestFieldValues($cardinality);
$entity->{$this->fieldTestData->field_name} = $values;
// Verify the field data is present on load.
$entity = $this->entitySaveReload($entity);
$this->assertEqual(count($entity->{$this->fieldTestData->field_name}), $cardinality, "Data is retrieved for the new bundle");
// Rename the bundle.
$new_bundle = 'test_bundle_' . Unicode::strtolower($this->randomMachineName());
entity_test_rename_bundle($this->fieldTestData->field_definition['bundle'], $new_bundle, $entity_type);
// Check that the field definition has been updated.
$this->fieldTestData->field = FieldConfig::loadByName($entity_type, $new_bundle, $this->fieldTestData->field_name);
$this->assertIdentical($this->fieldTestData->field->getTargetBundle(), $new_bundle, "Bundle name has been updated in the field.");
// Verify the field data is present on load.
$controller = $this->container->get('entity.manager')->getStorage($entity->getEntityTypeId());
$controller->resetCache();
$entity = $controller->load($entity->id());
$this->assertEqual(count($entity->{$this->fieldTestData->field_name}), $cardinality, "Bundle name has been updated in the field storage");
}
/**
* Test entity_bundle_delete().
*/
function testEntityDeleteBundle() {
$entity_type = 'entity_test_rev';
$this->createFieldWithStorage('', $entity_type);
// Create a new bundle.
$new_bundle = 'test_bundle_' . Unicode::strtolower($this->randomMachineName());
entity_test_create_bundle($new_bundle, NULL, $entity_type);
// Add a field to that bundle.
$this->fieldTestData->field_definition['bundle'] = $new_bundle;
entity_create('field_config', $this->fieldTestData->field_definition)->save();
// Create a second field for the test bundle
$field_name = Unicode::strtolower($this->randomMachineName() . '_field_name');
$field_storage = array(
'field_name' => $field_name,
'entity_type' => $entity_type,
'type' => 'test_field',
'cardinality' => 1,
);
entity_create('field_storage_config', $field_storage)->save();
$field = array(
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => $this->fieldTestData->field->getTargetBundle(),
'label' => $this->randomMachineName() . '_label',
'description' => $this->randomMachineName() . '_description',
'weight' => mt_rand(0, 127),
);
entity_create('field_config', $field)->save();
// Save an entity with data for both fields
$entity = entity_create($entity_type, array('type' => $this->fieldTestData->field->getTargetBundle()));
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage->getCardinality());
$entity->{$this->fieldTestData->field_name} = $values;
$entity->{$field_name} = $this->_generateTestFieldValues(1);
$entity = $this->entitySaveReload($entity);
// Verify the fields are present on load
$this->assertEqual(count($entity->{$this->fieldTestData->field_name}), 4, 'First field got loaded');
$this->assertEqual(count($entity->{$field_name}), 1, 'Second field got loaded');
// Delete the bundle.
entity_test_delete_bundle($this->fieldTestData->field->getTargetBundle(), $entity_type);
// Verify no data gets loaded
$controller = $this->container->get('entity.manager')->getStorage($entity->getEntityTypeId());
$controller->resetCache();
$entity= $controller->load($entity->id());
$this->assertTrue(empty($entity->{$this->fieldTestData->field_name}), 'No data for first field');
$this->assertTrue(empty($entity->{$field_name}), 'No data for second field');
// Verify that the fields are gone.
$this->assertFalse(FieldConfig::load('entity_test.' . $this->fieldTestData->field->getTargetBundle() . '.' . $this->fieldTestData->field_name), "First field is deleted");
$this->assertFalse(FieldConfig::load('entity_test.' . $field['bundle']. '.' . $field_name), "Second field is deleted");
}
}

View file

@ -0,0 +1,269 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldCrudTest.
*/
namespace Drupal\field\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Field\FieldException;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
/**
* Create field entities by attaching fields to entities.
*
* @group field
*/
class FieldCrudTest extends FieldUnitTestBase {
/**
* The field storage entity.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field entity definition.
*
* @var array
*/
protected $fieldStorageDefinition;
/**
* The field entity definition.
*
* @var array
*/
protected $fieldDefinition;
function setUp() {
parent::setUp();
$this->fieldStorageDefinition = array(
'field_name' => Unicode::strtolower($this->randomMachineName()),
'entity_type' => 'entity_test',
'type' => 'test_field',
);
$this->fieldStorage = entity_create('field_storage_config', $this->fieldStorageDefinition);
$this->fieldStorage->save();
$this->fieldDefinition = array(
'field_name' => $this->fieldStorage->getName(),
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
);
}
// TODO : test creation with
// - a full fledged $field structure, check that all the values are there
// - a minimal $field structure, check all default values are set
// defer actual $field comparison to a helper function, used for the two cases above,
// and for testUpdateField
/**
* Test the creation of a field.
*/
function testCreateField() {
// Set a state flag so that field_test.module knows to add an in-memory
// constraint for this field.
\Drupal::state()->set('field_test_add_constraint', $this->fieldStorage->getName());
/** @var \Drupal\Core\Field\FieldConfigInterface $field */
$field = entity_create('field_config', $this->fieldDefinition);
$field->save();
$field = FieldConfig::load($field->id());
$this->assertTrue($field->getSetting('field_setting_from_config_data'));
$this->assertNull($field->getSetting('config_data_from_field_setting'));
// Read the configuration. Check against raw configuration data rather than
// the loaded ConfigEntity, to be sure we check that the defaults are
// applied on write.
$config = $this->config('field.field.' . $field->id())->get();
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$this->assertTrue($config['settings']['config_data_from_field_setting']);
$this->assertTrue(!isset($config['settings']['field_setting_from_config_data']));
// Since we are working with raw configuration, this needs to be unset
// manually.
// @see Drupal\field_test\Plugin\Field\FieldType\TestItem::fieldSettingsFromConfigData()
unset($config['settings']['config_data_from_field_setting']);
// Check that default values are set.
$this->assertEqual($config['required'], FALSE, 'Required defaults to false.');
$this->assertIdentical($config['label'], $this->fieldDefinition['field_name'], 'Label defaults to field name.');
$this->assertIdentical($config['description'], '', 'Description defaults to empty string.');
// Check that default settings are set.
$this->assertEqual($config['settings'], $field_type_manager->getDefaultFieldSettings($this->fieldStorageDefinition['type']) , 'Default field settings have been written.');
// Check that the denormalized 'field_type' was properly written.
$this->assertEqual($config['field_type'], $this->fieldStorageDefinition['type']);
// Test constraints are applied. A Range constraint is added dynamically to
// limit the field to values between 0 and 32.
// @see field_test_entity_bundle_field_info_alter()
$this->doFieldValidationTests();
// Test FieldConfigBase::setPropertyConstraints().
\Drupal::state()->set('field_test_set_constraint', $this->fieldStorage->getName());
\Drupal::state()->set('field_test_add_constraint', FALSE);
\Drupal::entityManager()->clearCachedFieldDefinitions();
$this->doFieldValidationTests();
// Guarantee that the field/bundle combination is unique.
try {
entity_create('field_config', $this->fieldDefinition)->save();
$this->fail(t('Cannot create two fields with the same field / bundle combination.'));
}
catch (EntityStorageException $e) {
$this->pass(t('Cannot create two fields with the same field / bundle combination.'));
}
// Check that the specified field exists.
try {
$this->fieldDefinition['field_name'] = $this->randomMachineName();
entity_create('field_config', $this->fieldDefinition)->save();
$this->fail(t('Cannot create a field with a non-existing storage.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field with a non-existing storage.'));
}
// TODO: test other failures.
}
/**
* Test reading back a field definition.
*/
function testReadField() {
entity_create('field_config', $this->fieldDefinition)->save();
// Read the field back.
$field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
$this->assertTrue($this->fieldDefinition['field_name'] == $field->getName(), 'The field was properly read.');
$this->assertTrue($this->fieldDefinition['entity_type'] == $field->getTargetEntityTypeId(), 'The field was properly read.');
$this->assertTrue($this->fieldDefinition['bundle'] == $field->getTargetBundle(), 'The field was properly read.');
}
/**
* Test the update of a field.
*/
function testUpdateField() {
entity_create('field_config', $this->fieldDefinition)->save();
// Check that basic changes are saved.
$field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
$field->setRequired(!$field->isRequired());
$field->setLabel($this->randomMachineName());
$field->set('description', $this->randomMachineName());
$field->setSetting('test_field_setting', $this->randomMachineName());
$field->save();
$field_new = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
$this->assertEqual($field->isRequired(), $field_new->isRequired(), '"required" change is saved');
$this->assertEqual($field->getLabel(), $field_new->getLabel(), '"label" change is saved');
$this->assertEqual($field->getDescription(), $field_new->getDescription(), '"description" change is saved');
// TODO: test failures.
}
/**
* Test the deletion of a field.
*/
function testDeleteField() {
// TODO: Test deletion of the data stored in the field also.
// Need to check that data for a 'deleted' field / storage doesn't get loaded
// Need to check data marked deleted is cleaned on cron (not implemented yet...)
// Create two fields for the same field storage so we can test that only one
// is deleted.
entity_create('field_config', $this->fieldDefinition)->save();
$another_field_definition = $this->fieldDefinition;
$another_field_definition['bundle'] .= '_another_bundle';
entity_test_create_bundle($another_field_definition['bundle']);
entity_create('field_config', $another_field_definition)->save();
// Test that the first field is not deleted, and then delete it.
$field = current(entity_load_multiple_by_properties('field_config', array('entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE)));
$this->assertTrue(!empty($field) && empty($field->deleted), 'A new field is not marked for deletion.');
$field->delete();
// Make sure the field is marked as deleted when it is specifically loaded.
$field = current(entity_load_multiple_by_properties('field_config', array('entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE)));
$this->assertTrue($field->isDeleted(), 'A deleted field is marked for deletion.');
// Try to load the field normally and make sure it does not show up.
$field = FieldConfig::load('entity_test.' . '.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
$this->assertTrue(empty($field), 'A deleted field is not loaded by default.');
// Make sure the other field is not deleted.
$another_field = FieldConfig::load('entity_test.' . $another_field_definition['bundle'] . '.' . $another_field_definition['field_name']);
$this->assertTrue(!empty($another_field) && empty($another_field->deleted), 'A non-deleted field is not marked for deletion.');
}
/**
* Tests the cross deletion behavior between field storages and fields.
*/
function testDeleteFieldCrossDeletion() {
$field_definition_2 = $this->fieldDefinition;
$field_definition_2['bundle'] .= '_another_bundle';
entity_test_create_bundle($field_definition_2['bundle']);
// Check that deletion of a field storage deletes its fields.
$field_storage = $this->fieldStorage;
entity_create('field_config', $this->fieldDefinition)->save();
entity_create('field_config', $field_definition_2)->save();
$field_storage->delete();
$this->assertFalse(FieldConfig::loadByName('entity_test', $this->fieldDefinition['bundle'], $field_storage->getName()));
$this->assertFalse(FieldConfig::loadByName('entity_test', $field_definition_2['bundle'], $field_storage->getName()));
// Check that deletion of the last field deletes the storage.
$field_storage = entity_create('field_storage_config', $this->fieldStorageDefinition);
$field_storage->save();
$field = entity_create('field_config', $this->fieldDefinition);
$field->save();
$field_2 = entity_create('field_config', $field_definition_2);
$field_2->save();
$field->delete();
$this->assertTrue(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
$field_2->delete();
$this->assertFalse(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
// Check that deletion of all fields using a storage simultaneously deletes
// the storage.
$field_storage = entity_create('field_storage_config', $this->fieldStorageDefinition);
$field_storage->save();
$field = entity_create('field_config', $this->fieldDefinition);
$field->save();
$field_2 = entity_create('field_config', $field_definition_2);
$field_2->save();
$this->container->get('entity.manager')->getStorage('field_config')->delete(array($field, $field_2));
$this->assertFalse(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
}
/**
* Tests configurable field validation.
*
* @see field_test_entity_bundle_field_info_alter()
*/
protected function doFieldValidationTests() {
$entity = entity_create('entity_test');
$entity->set($this->fieldStorage->getName(), 1);
$violations = $entity->validate();
$this->assertEqual(count($violations), 0, 'No violations found when in-range value passed.');
$entity->set($this->fieldStorage->getName(), 33);
$violations = $entity->validate();
$this->assertEqual(count($violations), 1, 'Violations found when using value outside the range.');
$this->assertEqual($violations[0]->getPropertyPath(), $this->fieldStorage->getName() . '.0.value');
$this->assertEqual($violations[0]->getMessage(), t('This value should be %limit or less.', [
'%limit' => 32,
]));
}
}

View file

@ -0,0 +1,139 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldDataCountTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
/**
* Tests counting field data records and the hasData() method on
* FieldStorageConfig entity.
*
* @group field
* @see \Drupal\Core\Entity\FieldableEntityStorageInterface::countFieldData()
* @see \Drupal\field\Entity\FieldStorageConfig::hasData()
*/
class FieldDataCountTest extends FieldUnitTestBase {
/**
* @var \Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface
*/
protected $storage;
/**
* @var \Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface
*/
protected $storageRev;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_rev');
$this->storage = \Drupal::entityManager()->getStorage('entity_test');
$this->storageRev = \Drupal::entityManager()->getStorage('entity_test_rev');
}
/**
* Tests entityCount() and hadData() methods.
*/
public function testEntityCountAndHasData() {
// Create a field with a cardinality of 2 to show that we are counting
// entities and not rows in a table.
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_int',
'entity_type' => 'entity_test',
'type' => 'integer',
'cardinality' => 2,
));
$field_storage->save();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'entity_test',
))->save();
$this->assertIdentical($field_storage->hasdata(), FALSE, 'There are no entities with field data.');
$this->assertIdentical($this->storage->countFieldData($field_storage), 0, 'There are 0 entities with field data.');
// Create 1 entity without the field.
$entity = entity_create('entity_test');
$entity->name->value = $this->randomMachineName();
$entity->save();
$this->assertIdentical($field_storage->hasdata(), FALSE, 'There are no entities with field data.');
$this->assertIdentical($this->storage->countFieldData($field_storage), 0, 'There are 0 entities with field data.');
// Create 12 entities to ensure that the purging works as expected.
for ($i=0; $i < 12; $i++) {
$entity = entity_create('entity_test');
$entity->field_int[] = mt_rand(1,99);
$entity->field_int[] = mt_rand(1,99);
$entity->name[] = $this->randomMachineName();
$entity->save();
}
$storage = \Drupal::entityManager()->getStorage('entity_test');
if ($storage instanceof SqlContentEntityStorage) {
// Count the actual number of rows in the field table.
$table_mapping = $storage->getTableMapping();
$field_table_name = $table_mapping->getDedicatedDataTableName($field_storage);
$result = db_select($field_table_name, 't')
->fields('t')
->countQuery()
->execute()
->fetchField();
$this->assertEqual($result, 24, 'The field table has 24 rows.');
}
$this->assertIdentical($field_storage->hasdata(), TRUE, 'There are entities with field data.');
$this->assertEqual($this->storage->countFieldData($field_storage), 12, 'There are 12 entities with field data.');
// Ensure the methods work on deleted fields.
$field_storage->delete();
$this->assertIdentical($field_storage->hasdata(), TRUE, 'There are entities with deleted field data.');
$this->assertEqual($this->storage->countFieldData($field_storage), 12, 'There are 12 entities with deleted field data.');
field_purge_batch(6);
$this->assertIdentical($field_storage->hasdata(), TRUE, 'There are entities with deleted field data.');
$this->assertEqual($this->storage->countFieldData($field_storage), 6, 'There are 6 entities with deleted field data.');
$entity_type = 'entity_test_rev';
$this->createFieldWithStorage('_2', $entity_type);
$entity_init = entity_create($entity_type, array(
'type' => $entity_type,
));
$cardinality = $this->fieldTestData->field_storage_2->getCardinality();
$this->assertIdentical($this->fieldTestData->field_storage_2->hasData(), FALSE, 'There are no entities with field data.');
$this->assertIdentical($this->storageRev->countFieldData($this->fieldTestData->field_storage_2), 0, 'There are 0 entities with field data.');
// Create 1 entity with the field.
$entity = clone($entity_init);
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage_2->getCardinality());
$entity->{$this->fieldTestData->field_name_2} = $values;
$entity->setNewRevision();
$entity->save();
$first_revision = $entity->getRevisionId();
$this->assertIdentical($this->fieldTestData->field_storage_2->hasData(), TRUE, 'There are entities with field data.');
$this->assertIdentical($this->storageRev->countFieldData($this->fieldTestData->field_storage_2), 1, 'There is 1 entity with field data.');
$entity->{$this->fieldTestData->field_name_2} = array();
$entity->setNewRevision();
$entity->save();
$this->assertIdentical($this->fieldTestData->field_storage_2->hasData(), TRUE, 'There are entities with field data.');
$storage = $this->container->get('entity.manager')->getStorage($entity_type);
$entity = $storage->loadRevision($first_revision);
$this->assertEqual(count($entity->{$this->fieldTestData->field_name_2}), $cardinality, format_string('Revision %revision_id: expected number of values.', array('%revision_id' => $first_revision)));
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldDefinitionIntegrityTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Extension\Extension;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the integrity of field API plugin definitions.
*
* @group field
*/
class FieldDefinitionIntegrityTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable all core modules that provide field plugins.
$modules = system_rebuild_module_data();
$modules = array_filter($modules, function (Extension $module) {
// Filter contrib, hidden, already enabled modules and modules in the
// Testing package.
if ($module->origin === 'core'
&& empty($module->info['hidden'])
&& $module->status == FALSE
&& $module->info['package'] !== 'Testing'
&& is_readable($module->getPath() . '/src/Plugin/Field')) {
return TRUE;
}
return FALSE;
});
$this->enableModules(array_keys($modules));
}
/**
* Tests the integrity of field plugin definitions.
*/
public function testFieldPluginDefinitionIntegrity() {
// Load the IDs of all available field type plugins.
$available_field_type_ids = [];
/** @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
foreach ($field_type_manager->getDefinitions() as $definition) {
$available_field_type_ids[] = $definition['id'];
}
// Test the field widget plugins.
/** @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface $field_widget_manager */
$field_widget_manager = \Drupal::service('plugin.manager.field.widget');
foreach ($field_widget_manager->getDefinitions() as $definition) {
$missing_field_type_ids = array_diff($definition['field_types'], $available_field_type_ids);
if ($missing_field_type_ids) {
$this->fail(sprintf('Field widget %s integrates with non-existent field types: %s', $definition['id'], implode(', ', $missing_field_type_ids)));
}
else {
$this->pass(sprintf('Field widget %s integrates with existing field types.', $definition['id']));
}
}
// Test the field formatter plugins.
/** @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface $field_formatter_manager */
$field_formatter_manager = \Drupal::service('plugin.manager.field.formatter');
foreach ($field_formatter_manager->getDefinitions() as $definition) {
$missing_field_type_ids = array_diff($definition['field_types'], $available_field_type_ids);
if ($missing_field_type_ids) {
$this->fail(sprintf('Field formatter %s integrates with non-existent field types: %s', $definition['id'], implode(', ', $missing_field_type_ids)));
}
else {
$this->pass(sprintf('Field formatter %s integrates with existing field types.', $definition['id']));
}
}
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldHelpTest.
*/
namespace Drupal\field\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests help display for the Field module.
*
* @group field
*/
class FieldHelpTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array.
*/
public static $modules = array('field', 'help');
// Tests field help implementation without optional core modules enabled.
protected $profile = 'minimal';
/**
* The admin user that will be created.
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
// Create the admin user.
$this->adminUser = $this->drupalCreateUser(array('access administration pages', 'view the administration theme'));
}
/**
* Test the Field module's help page.
*/
public function testFieldHelp() {
// Login the admin user.
$this->drupalLogin($this->adminUser);
// Visit the Help page and make sure no warnings or notices are thrown.
$this->drupalGet('admin/help/field');
// Enable the Options, Email and Field API Test modules.
\Drupal::service('module_installer')->install(array('options', 'field_test'));
$this->resetAll();
\Drupal::service('plugin.manager.field.widget')->clearCachedDefinitions();
\Drupal::service('plugin.manager.field.field_type')->clearCachedDefinitions();
$this->drupalGet('admin/help/field');
$this->assertLink('Options', 0, 'Options module is listed on the Field help page.');
$this->assertText('Field API Test', 'Modules with field types that do not implement hook_help are listed.');
$this->assertNoLink('Field API Test', 'Modules with field types that do not implement hook_help are not linked.');
$this->assertNoLink('Link', 'Modules that have not been installed, are not listed.');
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldImportChangeTest.
*/
namespace Drupal\field\Tests;
use Drupal\field\Entity\FieldConfig;
/**
* Update field storage and fields during config change method invocation.
*
* @group field
*/
class FieldImportChangeTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* The default configuration provided by field_test_config is imported by
* \Drupal\field\Tests\FieldUnitTestBase::setUp() when it installs field
* configuration.
*
* @var array
*/
public static $modules = array('field_test_config');
/**
* Tests importing an updated field.
*/
function testImportChange() {
$this->installConfig(['field_test_config']);
$field_storage_id = 'field_test_import';
$field_id = "entity_test.entity_test.$field_storage_id";
$field_config_name = "field.field.$field_id";
$active = $this->container->get('config.storage');
$staging = $this->container->get('config.storage.staging');
$this->copyConfig($active, $staging);
// Save as files in the staging directory.
$field = $active->read($field_config_name);
$new_label = 'Test update import field';
$field['label'] = $new_label;
$staging->write($field_config_name, $field);
// Import the content of the staging directory.
$this->configImporter()->import();
// Check that the updated config was correctly imported.
$field = FieldConfig::load($field_id);
$this->assertEqual($field->getLabel(), $new_label, 'field label updated');
}
}

View file

@ -0,0 +1,124 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldImportCreateTest.
*/
namespace Drupal\field\Tests;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Create field storages and fields during config create method invocation.
*
* @group field
*/
class FieldImportCreateTest extends FieldUnitTestBase {
/**
* Tests creating field storages and fields during default config import.
*/
function testImportCreateDefault() {
$field_name = 'field_test_import';
$field_storage_id = "entity_test.$field_name";
$field_id = "entity_test.entity_test.$field_name";
$field_name_2 = 'field_test_import_2';
$field_storage_id_2 = "entity_test.$field_name_2";
$field_id_2a = "entity_test.entity_test.$field_name_2";
$field_id_2b = "entity_test.test_bundle.$field_name_2";
// Check that the field storages and fields do not exist yet.
$this->assertFalse(FieldStorageConfig::load($field_storage_id));
$this->assertFalse(FieldConfig::load($field_id));
$this->assertFalse(FieldStorageConfig::load($field_storage_id_2));
$this->assertFalse(FieldConfig::load($field_id_2a));
$this->assertFalse(FieldConfig::load($field_id_2b));
// Create a second bundle for the 'Entity test' entity type.
entity_test_create_bundle('test_bundle');
// Enable field_test_config module and check that the field and storage
// shipped in the module's default config were created.
\Drupal::service('module_installer')->install(array('field_test_config'));
// A field storage with one single field.
$field_storage = FieldStorageConfig::load($field_storage_id);
$this->assertTrue($field_storage, 'The field was created.');
$field = FieldConfig::load($field_id);
$this->assertTrue($field, 'The field was deleted.');
// A field storage with two fields.
$field_storage_2 = FieldStorageConfig::load($field_storage_id_2);
$this->assertTrue($field_storage_2, 'The second field was created.');
$this->assertTrue($field->getTargetBundle(), 'test_bundle', 'The second field was created on bundle test_bundle.');
$this->assertTrue($field->getTargetBundle(), 'test_bundle_2', 'The second field was created on bundle test_bundle_2.');
// Tests fields.
$ids = \Drupal::entityQuery('field_config')
->condition('entity_type', 'entity_test')
->condition('bundle', 'entity_test')
->execute();
$this->assertEqual(count($ids), 2);
$this->assertTrue(isset($ids['entity_test.entity_test.field_test_import']));
$this->assertTrue(isset($ids['entity_test.entity_test.field_test_import_2']));
$ids = \Drupal::entityQuery('field_config')
->condition('entity_type', 'entity_test')
->condition('bundle', 'test_bundle')
->execute();
$this->assertEqual(count($ids), 1);
$this->assertTrue(isset($ids['entity_test.test_bundle.field_test_import_2']));
}
/**
* Tests creating field storages and fields during config import.
*/
function testImportCreate() {
// A field storage with one single field.
$field_name = 'field_test_import_staging';
$field_storage_id = "entity_test.$field_name";
$field_id = "entity_test.entity_test.$field_name";
$field_storage_config_name = "field.storage.$field_storage_id";
$field_config_name = "field.field.$field_id";
// A field storage with two fields.
$field_name_2 = 'field_test_import_staging_2';
$field_storage_id_2 = "entity_test.$field_name_2";
$field_id_2a = "entity_test.test_bundle.$field_name_2";
$field_id_2b = "entity_test.test_bundle_2.$field_name_2";
$field_storage_config_name_2 = "field.storage.$field_storage_id_2";
$field_config_name_2a = "field.field.$field_id_2a";
$field_config_name_2b = "field.field.$field_id_2b";
$active = $this->container->get('config.storage');
$staging = $this->container->get('config.storage.staging');
$this->copyConfig($active, $staging);
// Add the new files to the staging directory.
$src_dir = drupal_get_path('module', 'field_test_config') . '/staging';
$target_dir = $this->configDirectories[CONFIG_STAGING_DIRECTORY];
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_storage_config_name.yml", "$target_dir/$field_storage_config_name.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name.yml", "$target_dir/$field_config_name.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_storage_config_name_2.yml", "$target_dir/$field_storage_config_name_2.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name_2a.yml", "$target_dir/$field_config_name_2a.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name_2b.yml", "$target_dir/$field_config_name_2b.yml"));
// Import the content of the staging directory.
$this->configImporter()->import();
// Check that the field and storage were created.
$field_storage = FieldStorageConfig::load($field_storage_id);
$this->assertTrue($field_storage, 'Test import storage field from staging exists');
$field = FieldConfig::load($field_id);
$this->assertTrue($field, 'Test import field from staging exists');
$field_storage = FieldStorageConfig::load($field_storage_id_2);
$this->assertTrue($field_storage, 'Test import storage field 2 from staging exists');
$field = FieldConfig::load($field_id_2a);
$this->assertTrue($field, 'Test import field 2a from staging exists');
$field = FieldConfig::load($field_id_2b);
$this->assertTrue($field, 'Test import field 2b from staging exists');
}
}

View file

@ -0,0 +1,117 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldImportDeleteTest.
*/
namespace Drupal\field\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Delete field storages and fields during config delete method invocation.
*
* @group field
*/
class FieldImportDeleteTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* The default configuration provided by field_test_config is imported by
* \Drupal\field\Tests\FieldUnitTestBase::setUp() when it installs field
* configuration.
*
* @var array
*/
public static $modules = array('field_test_config');
/**
* Tests deleting field storages and fields as part of config import.
*/
public function testImportDelete() {
$this->installConfig(['field_test_config']);
// At this point there are 5 field configuration objects in the active
// storage.
// - field.storage.entity_test.field_test_import
// - field.storage.entity_test.field_test_import_2
// - field.field.entity_test.entity_test.field_test_import
// - field.field.entity_test.entity_test.field_test_import_2
// - field.field.entity_test.test_bundle.field_test_import_2
$field_name = 'field_test_import';
$field_storage_id = "entity_test.$field_name";
$field_name_2 = 'field_test_import_2';
$field_storage_id_2 = "entity_test.$field_name_2";
$field_id = "entity_test.entity_test.$field_name";
$field_id_2a = "entity_test.entity_test.$field_name_2";
$field_id_2b = "entity_test.test_bundle.$field_name_2";
$field_storage_config_name = "field.storage.$field_storage_id";
$field_storage_config_name_2 = "field.storage.$field_storage_id_2";
$field_config_name = "field.field.$field_id";
$field_config_name_2a = "field.field.$field_id_2a";
$field_config_name_2b = "field.field.$field_id_2b";
// Create a second bundle for the 'Entity test' entity type.
entity_test_create_bundle('test_bundle');
// Get the uuid's for the field storages.
$field_storage_uuid = FieldStorageConfig::load($field_storage_id)->uuid();
$field_storage_uuid_2 = FieldStorageConfig::load($field_storage_id_2)->uuid();
$active = $this->container->get('config.storage');
$staging = $this->container->get('config.storage.staging');
$this->copyConfig($active, $staging);
$this->assertTrue($staging->delete($field_storage_config_name), SafeMarkup::format('Deleted field storage: !field_storage', array('!field_storage' => $field_storage_config_name)));
$this->assertTrue($staging->delete($field_storage_config_name_2), SafeMarkup::format('Deleted field storage: !field_storage', array('!field_storage' => $field_storage_config_name_2)));
$this->assertTrue($staging->delete($field_config_name), SafeMarkup::format('Deleted field: !field', array('!field' => $field_config_name)));
$this->assertTrue($staging->delete($field_config_name_2a), SafeMarkup::format('Deleted field: !field', array('!field' => $field_config_name_2a)));
$this->assertTrue($staging->delete($field_config_name_2b), SafeMarkup::format('Deleted field: !field', array('!field' => $field_config_name_2b)));
$deletes = $this->configImporter()->getUnprocessedConfiguration('delete');
$this->assertEqual(count($deletes), 5, 'Importing configuration will delete 3 fields and 2 field storages.');
// Import the content of the staging directory.
$this->configImporter()->import();
// Check that the field storages and fields are gone.
\Drupal::entityManager()->getStorage('field_storage_config')->resetCache(array($field_storage_id));
$field_storage = FieldStorageConfig::load($field_storage_id);
$this->assertFalse($field_storage, 'The field storage was deleted.');
\Drupal::entityManager()->getStorage('field_storage_config')->resetCache(array($field_storage_id_2));
$field_storage_2 = FieldStorageConfig::load($field_storage_id_2);
$this->assertFalse($field_storage_2, 'The second field storage was deleted.');
\Drupal::entityManager()->getStorage('field_config')->resetCache(array($field_id));
$field = FieldConfig::load($field_id);
$this->assertFalse($field, 'The field was deleted.');
\Drupal::entityManager()->getStorage('field_config')->resetCache(array($field_id_2a));
$field_2a = FieldConfig::load($field_id_2a);
$this->assertFalse($field_2a, 'The second field on test bundle was deleted.');
\Drupal::entityManager()->getStorage('field_config')->resetCache(array($field_id_2b));
$field_2b = FieldConfig::load($field_id_2b);
$this->assertFalse($field_2b, 'The second field on test bundle 2 was deleted.');
// Check that all config files are gone.
$active = $this->container->get('config.storage');
$this->assertIdentical($active->listAll($field_storage_config_name), array());
$this->assertIdentical($active->listAll($field_storage_config_name_2), array());
$this->assertIdentical($active->listAll($field_config_name), array());
$this->assertIdentical($active->listAll($field_config_name_2a), array());
$this->assertIdentical($active->listAll($field_config_name_2b), array());
// Check that the storage definition is preserved in state.
$deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
$this->assertTrue(isset($deleted_storages[$field_storage_uuid]));
$this->assertTrue(isset($deleted_storages[$field_storage_uuid_2]));
// Purge field data, and check that the storage definition has been
// completely removed once the data is purged.
field_purge_batch(10);
$deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
$this->assertTrue(empty($deleted_storages), 'Fields are deleted');
}
}

View file

@ -0,0 +1,169 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldImportDeleteUninstallTest.
*/
namespace Drupal\field\Tests;
/**
* Delete field storages and fields during config synchronization and uninstall
* module that provides the field type.
*
* @group field
* @see \Drupal\field\ConfigImporterFieldPurger
* @see field_config_import_steps_alter()
*/
class FieldImportDeleteUninstallTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('telephone');
protected function setUp() {
parent::setUp();
// Module uninstall requires the router and users_data tables.
// @see drupal_flush_all_caches()
// @see user_modules_uninstalled()
$this->installSchema('user', array('users_data'));
}
/**
* Tests deleting field storages and fields as part of config import.
*/
public function testImportDeleteUninstall() {
// Create a field to delete to prove that
// \Drupal\field\ConfigImporterFieldPurger does not purge fields that are
// not related to the configuration synchronization.
$unrelated_field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_int',
'entity_type' => 'entity_test',
'type' => 'integer',
));
$unrelated_field_storage->save();
entity_create('field_config', array(
'field_storage' => $unrelated_field_storage,
'bundle' => 'entity_test',
))->save();
// Create a telephone field for validation.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_test',
'entity_type' => 'entity_test',
'type' => 'telephone',
));
$field_storage->save();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'entity_test',
))->save();
$entity = entity_create('entity_test');
$value = '+0123456789';
$entity->field_test = $value;
$entity->field_int = '99';
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->field_test->value, $value);
$this->assertEqual($entity->field_test[0]->value, $value);
$this->assertEqual($entity->field_int->value, '99');
// Delete unrelated field before copying configuration and running the
// synchronization.
$unrelated_field_storage->delete();
$active = $this->container->get('config.storage');
$staging = $this->container->get('config.storage.staging');
$this->copyConfig($active, $staging);
// Stage uninstall of the Telephone module.
$core_extension = $this->config('core.extension')->get();
unset($core_extension['module']['telephone']);
$staging->write('core.extension', $core_extension);
// Stage the field deletion
$staging->delete('field.storage.entity_test.field_test');
$staging->delete('field.field.entity_test.entity_test.field_test');
$steps = $this->configImporter()->initialize();
$this->assertIdentical($steps[0], array('\Drupal\field\ConfigImporterFieldPurger', 'process'), 'The additional process configuration synchronization step has been added.');
// This will purge all the data, delete the field and uninstall the
// Telephone module.
$this->configImporter()->import();
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
$this->assertFalse(\Drupal::entityManager()->loadEntityByUuid('field_storage_config', $field_storage->uuid()), 'The test field has been deleted by the configuration synchronization');
$deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
$this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Telephone field has been completed removed from the system.');
$this->assertTrue(isset($deleted_storages[$unrelated_field_storage->uuid()]), 'Unrelated field not purged by configuration synchronization.');
}
/**
* Tests purging already deleted field storages and fields during a config
* import.
*/
public function testImportAlreadyDeletedUninstall() {
// Create a telephone field for validation.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_test',
'entity_type' => 'entity_test',
'type' => 'telephone',
));
$field_storage->save();
$field_storage_uuid = $field_storage->uuid();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'entity_test',
))->save();
// Create 12 entities to ensure that the purging works as expected.
for ($i=0; $i < 12; $i++) {
$entity = entity_create('entity_test');
$value = '+0123456789';
$entity->field_test = $value;
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->field_test->value, $value);
}
// Delete the field.
$field_storage->delete();
$active = $this->container->get('config.storage');
$staging = $this->container->get('config.storage.staging');
$this->copyConfig($active, $staging);
// Stage uninstall of the Telephone module.
$core_extension = $this->config('core.extension')->get();
unset($core_extension['module']['telephone']);
$staging->write('core.extension', $core_extension);
$deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
$this->assertTrue(isset($deleted_storages[$field_storage_uuid]), 'Field has been deleted and needs purging before configuration synchronization.');
$steps = $this->configImporter()->initialize();
$this->assertIdentical($steps[0], array('\Drupal\field\ConfigImporterFieldPurger', 'process'), 'The additional process configuration synchronization step has been added.');
// This will purge all the data, delete the field and uninstall the
// Telephone module.
$this->configImporter()->import();
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
$deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
$this->assertFalse(isset($deleted_storages[$field_storage_uuid]), 'Field has been completed removed from the system.');
}
}

View file

@ -0,0 +1,118 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldImportDeleteUninstallUiTest.
*/
namespace Drupal\field\Tests;
/**
* Delete field storages and fields during config synchronization and uninstall
* module that provides the field type through the UI.
*
* @group field
* @see \Drupal\field\ConfigImporterFieldPurger
* @see field_config_import_steps_alter()
* @see field_form_config_admin_import_form_alter()
*/
class FieldImportDeleteUninstallUiTest extends FieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_test', 'telephone', 'config', 'filter', 'datetime');
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(array('synchronize configuration')));
}
/**
* Tests deleting field storages and fields as part of config import.
*/
public function testImportDeleteUninstall() {
// Create a telephone field.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_tel',
'entity_type' => 'entity_test',
'type' => 'telephone',
));
$field_storage->save();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'entity_test',
))->save();
// Create a text field.
$date_field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_date',
'entity_type' => 'entity_test',
'type' => 'datetime',
));
$date_field_storage->save();
entity_create('field_config', array(
'field_storage' => $date_field_storage,
'bundle' => 'entity_test',
))->save();
// Create an entity which has values for the telephone and text field.
$entity = entity_create('entity_test');
$value = '+0123456789';
$entity->field_tel = $value;
$entity->field_date = time();
$entity->name->value = $this->randomMachineName();
$entity->save();
// Delete the text field before exporting configuration so that we can test
// that deleted fields that are provided by modules that will be uninstalled
// are also purged and that the UI message includes such fields.
$date_field_storage->delete();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->field_tel->value, $value);
$this->assertEqual($entity->field_tel[0]->value, $value);
$active = $this->container->get('config.storage');
$staging = $this->container->get('config.storage.staging');
$this->copyConfig($active, $staging);
// Stage uninstall of the Telephone module.
$core_extension = $this->config('core.extension')->get();
unset($core_extension['module']['telephone']);
$staging->write('core.extension', $core_extension);
// Stage the field deletion
$staging->delete('field.storage.entity_test.field_tel');
$staging->delete('field.field.entity_test.entity_test.field_tel');
$this->drupalGet('admin/config/development/configuration');
// Test that the message for one field being purged during a configuration
// synchronization is correct.
$this->assertText('This synchronization will delete data from the field entity_test.field_tel.');
// Stage an uninstall of the datetime module to test the message for
// multiple fields.
unset($core_extension['module']['datetime']);
$staging->write('core.extension', $core_extension);
$this->drupalGet('admin/config/development/configuration');
$this->assertText('This synchronization will delete data from the fields: entity_test.field_tel, entity_test.field_date.');
// This will purge all the data, delete the field and uninstall the
// Telephone and Text modules.
$this->drupalPostForm(NULL, array(), t('Import all'));
$this->assertNoText('Field data will be deleted by this synchronization.');
$this->rebuildContainer();
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
$this->assertFalse(\Drupal::entityManager()->loadEntityByUuid('field_storage_config', $field_storage->uuid()), 'The telephone field has been deleted by the configuration synchronization');
$deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
$this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Telephone field has been completed removed from the system.');
$this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Text field has been completed removed from the system.');
}
}

View file

@ -0,0 +1,461 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldStorageCrudTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Field\FieldException;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests field storage create, read, update, and delete.
*
* @group field
*/
class FieldStorageCrudTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array();
// TODO : test creation with
// - a full fledged $field structure, check that all the values are there
// - a minimal $field structure, check all default values are set
// defer actual $field comparison to a helper function, used for the two cases above
/**
* Test the creation of a field storage.
*/
function testCreate() {
$field_storage_definition = array(
'field_name' => 'field_2',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
field_test_memorize();
$field_storage = entity_create('field_storage_config', $field_storage_definition);
$field_storage->save();
$field_storage = FieldStorageConfig::load($field_storage->id());
$this->assertTrue($field_storage->getSetting('storage_setting_from_config_data'));
$this->assertNull($field_storage->getSetting('config_data_from_storage_setting'));
$mem = field_test_memorize();
$this->assertIdentical($mem['field_test_field_storage_config_create'][0][0]->getName(), $field_storage_definition['field_name'], 'hook_entity_create() called with correct arguments.');
$this->assertIdentical($mem['field_test_field_storage_config_create'][0][0]->getType(), $field_storage_definition['type'], 'hook_entity_create() called with correct arguments.');
// Read the configuration. Check against raw configuration data rather than
// the loaded ConfigEntity, to be sure we check that the defaults are
// applied on write.
$field_storage_config = $this->config('field.storage.' . $field_storage->id())->get();
$this->assertTrue($field_storage_config['settings']['config_data_from_storage_setting']);
$this->assertTrue(!isset($field_storage_config['settings']['storage_setting_from_config_data']));
// Since we are working with raw configuration, this needs to be unset
// manually.
// @see Drupal\field_test\Plugin\Field\FieldType\TestItem::storageSettingsFromConfigData()
unset($field_storage_config['settings']['config_data_from_storage_setting']);
// Ensure that basic properties are preserved.
$this->assertEqual($field_storage_config['field_name'], $field_storage_definition['field_name'], 'The field name is properly saved.');
$this->assertEqual($field_storage_config['entity_type'], $field_storage_definition['entity_type'], 'The field entity type is properly saved.');
$this->assertEqual($field_storage_config['id'], $field_storage_definition['entity_type'] . '.' . $field_storage_definition['field_name'], 'The field id is properly saved.');
$this->assertEqual($field_storage_config['type'], $field_storage_definition['type'], 'The field type is properly saved.');
// Ensure that cardinality defaults to 1.
$this->assertEqual($field_storage_config['cardinality'], 1, 'Cardinality defaults to 1.');
// Ensure that default settings are present.
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$this->assertEqual($field_storage_config['settings'], $field_type_manager->getDefaultStorageSettings($field_storage_definition['type']), 'Default storage settings have been written.');
// Guarantee that the name is unique.
try {
entity_create('field_storage_config', $field_storage_definition)->save();
$this->fail(t('Cannot create two fields with the same name.'));
}
catch (EntityStorageException $e) {
$this->pass(t('Cannot create two fields with the same name.'));
}
// Check that field type is required.
try {
$field_storage_definition = array(
'field_name' => 'field_1',
'entity_type' => 'entity_type',
);
entity_create('field_storage_config', $field_storage_definition)->save();
$this->fail(t('Cannot create a field with no type.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field with no type.'));
}
// Check that field name is required.
try {
$field_storage_definition = array(
'type' => 'test_field',
'entity_type' => 'entity_test',
);
entity_create('field_storage_config', $field_storage_definition)->save();
$this->fail(t('Cannot create an unnamed field.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create an unnamed field.'));
}
// Check that entity type is required.
try {
$field_storage_definition = array(
'field_name' => 'test_field',
'type' => 'test_field'
);
entity_create('field_storage_config', $field_storage_definition)->save();
$this->fail('Cannot create a field without an entity type.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field without an entity type.');
}
// Check that field name must start with a letter or _.
try {
$field_storage_definition = array(
'field_name' => '2field_2',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_storage_config', $field_storage_definition)->save();
$this->fail(t('Cannot create a field with a name starting with a digit.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field with a name starting with a digit.'));
}
// Check that field name must only contain lowercase alphanumeric or _.
try {
$field_storage_definition = array(
'field_name' => 'field#_3',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_storage_config', $field_storage_definition)->save();
$this->fail(t('Cannot create a field with a name containing an illegal character.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field with a name containing an illegal character.'));
}
// Check that field name cannot be longer than 32 characters long.
try {
$field_storage_definition = array(
'field_name' => '_12345678901234567890123456789012',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
entity_create('field_storage_config', $field_storage_definition)->save();
$this->fail(t('Cannot create a field with a name longer than 32 characters.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field with a name longer than 32 characters.'));
}
// Check that field name can not be an entity key.
// "id" is known as an entity key from the "entity_test" type.
try {
$field_storage_definition = array(
'type' => 'test_field',
'field_name' => 'id',
'entity_type' => 'entity_test',
);
entity_create('field_storage_config', $field_storage_definition)->save();
$this->fail(t('Cannot create a field bearing the name of an entity key.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field bearing the name of an entity key.'));
}
}
/**
* Tests that an explicit schema can be provided on creation.
*
* This behavior is needed to allow field storage creation within updates,
* since plugin classes (and thus the field type schema) cannot be accessed.
*/
function testCreateWithExplicitSchema() {
$schema = array(
'dummy' => 'foobar'
);
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_2',
'entity_type' => 'entity_test',
'type' => 'test_field',
'schema' => $schema,
));
$this->assertEqual($field_storage->getSchema(), $schema);
}
/**
* Tests reading field storage definitions.
*/
function testRead() {
$field_storage_definition = array(
'field_name' => 'field_1',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
$field_storage = entity_create('field_storage_config', $field_storage_definition);
$field_storage->save();
$id = $field_storage->id();
// Check that 'single column' criteria works.
$fields = entity_load_multiple_by_properties('field_storage_config', array('field_name' => $field_storage_definition['field_name']));
$this->assertTrue(count($fields) == 1 && isset($fields[$id]), 'The field was properly read.');
// Check that 'multi column' criteria works.
$fields = entity_load_multiple_by_properties('field_storage_config', array('field_name' => $field_storage_definition['field_name'], 'type' => $field_storage_definition['type']));
$this->assertTrue(count($fields) == 1 && isset($fields[$id]), 'The field was properly read.');
$fields = entity_load_multiple_by_properties('field_storage_config', array('field_name' => $field_storage_definition['field_name'], 'type' => 'foo'));
$this->assertTrue(empty($fields), 'No field was found.');
// Create a field from the field storage.
$field_definition = array(
'field_name' => $field_storage_definition['field_name'],
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
);
entity_create('field_config', $field_definition)->save();
}
/**
* Test creation of indexes on data column.
*/
function testIndexes() {
// Check that indexes specified by the field type are used by default.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_1',
'entity_type' => 'entity_test',
'type' => 'test_field',
));
$field_storage->save();
$field_storage = FieldStorageConfig::load($field_storage->id());
$schema = $field_storage->getSchema();
$expected_indexes = array('value' => array('value'));
$this->assertEqual($schema['indexes'], $expected_indexes, 'Field type indexes saved by default');
// Check that indexes specified by the field definition override the field
// type indexes.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_2',
'entity_type' => 'entity_test',
'type' => 'test_field',
'indexes' => array(
'value' => array(),
),
));
$field_storage->save();
$field_storage = FieldStorageConfig::load($field_storage->id());
$schema = $field_storage->getSchema();
$expected_indexes = array('value' => array());
$this->assertEqual($schema['indexes'], $expected_indexes, 'Field definition indexes override field type indexes');
// Check that indexes specified by the field definition add to the field
// type indexes.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_3',
'entity_type' => 'entity_test',
'type' => 'test_field',
'indexes' => array(
'value_2' => array('value'),
),
));
$field_storage->save();
$id = $field_storage->id();
$field_storage = FieldStorageConfig::load($id);
$schema = $field_storage->getSchema();
$expected_indexes = array('value' => array('value'), 'value_2' => array('value'));
$this->assertEqual($schema['indexes'], $expected_indexes, 'Field definition indexes are merged with field type indexes');
}
/**
* Test the deletion of a field storage.
*/
function testDelete() {
// TODO: Also test deletion of the data stored in the field ?
// Create two fields (so we can test that only one is deleted).
$field_storage_definition = array(
'field_name' => 'field_1',
'type' => 'test_field',
'entity_type' => 'entity_test',
);
entity_create('field_storage_config', $field_storage_definition)->save();
$another_field_storage_definition = array(
'field_name' => 'field_2',
'type' => 'test_field',
'entity_type' => 'entity_test',
);
entity_create('field_storage_config', $another_field_storage_definition)->save();
// Create fields for each.
$field_definition = array(
'field_name' => $field_storage_definition['field_name'],
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
);
entity_create('field_config', $field_definition)->save();
$another_field_definition = $field_definition;
$another_field_definition['field_name'] = $another_field_storage_definition['field_name'];
entity_create('field_config', $another_field_definition)->save();
// Test that the first field is not deleted, and then delete it.
$field_storage = current(entity_load_multiple_by_properties('field_storage_config', array('field_name' => $field_storage_definition['field_name'], 'include_deleted' => TRUE)));
$this->assertTrue(!empty($field_storage) && !$field_storage->isDeleted(), 'A new storage is not marked for deletion.');
FieldStorageConfig::loadByName('entity_test', $field_storage_definition['field_name'])->delete();
// Make sure that the field is marked as deleted when it is specifically
// loaded.
$field_storage = current(entity_load_multiple_by_properties('field_storage_config', array('field_name' => $field_storage_definition['field_name'], 'include_deleted' => TRUE)));
$this->assertTrue($field_storage->isDeleted(), 'A deleted storage is marked for deletion.');
// Make sure that this field is marked as deleted when it is
// specifically loaded.
$field = current(entity_load_multiple_by_properties('field_config', array('entity_type' => 'entity_test', 'field_name' => $field_definition['field_name'], 'bundle' => $field_definition['bundle'], 'include_deleted' => TRUE)));
$this->assertTrue($field->isDeleted(), 'A field whose storage was deleted is marked for deletion.');
// Try to load the storage normally and make sure it does not show up.
$field_storage = FieldStorageConfig::load('entity_test.' . $field_storage_definition['field_name']);
$this->assertTrue(empty($field_storage), 'A deleted storage is not loaded by default.');
// Try to load the field normally and make sure it does not show up.
$field = FieldConfig::load('entity_test.' . '.' . $field_definition['bundle'] . '.' . $field_definition['field_name']);
$this->assertTrue(empty($field), 'A field whose storage was deleted is not loaded by default.');
// Make sure the other field and its storage are not deleted.
$another_field_storage = FieldStorageConfig::load('entity_test.' . $another_field_storage_definition['field_name']);
$this->assertTrue(!empty($another_field_storage) && !$another_field_storage->isDeleted(), 'A non-deleted storage is not marked for deletion.');
$another_field = FieldConfig::load('entity_test.' . $another_field_definition['bundle'] . '.' . $another_field_definition['field_name']);
$this->assertTrue(!empty($another_field) && !$another_field->isDeleted(), 'A field whose storage was not deleted is not marked for deletion.');
// Try to create a new field the same name as a deleted field and
// write data into it.
entity_create('field_storage_config', $field_storage_definition)->save();
entity_create('field_config', $field_definition)->save();
$field_storage = FieldStorageConfig::load('entity_test.' . $field_storage_definition['field_name']);
$this->assertTrue(!empty($field_storage) && !$field_storage->isDeleted(), 'A new storage with a previously used name is created.');
$field = FieldConfig::load('entity_test.' . $field_definition['bundle'] . '.' . $field_definition['field_name'] );
$this->assertTrue(!empty($field) && !$field->isDeleted(), 'A new field for a previously used field name is created.');
// Save an entity with data for the field
$entity = entity_create('entity_test');
$values[0]['value'] = mt_rand(1, 127);
$entity->{$field_storage->getName()}->value = $values[0]['value'];
$entity = $this->entitySaveReload($entity);
// Verify the field is present on load
$this->assertIdentical(count($entity->{$field_storage->getName()}), count($values), "Data in previously deleted field saves and loads correctly");
foreach ($values as $delta => $value) {
$this->assertEqual($entity->{$field_storage->getName()}[$delta]->value, $values[$delta]['value'], "Data in previously deleted field saves and loads correctly");
}
}
function testUpdateFieldType() {
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_type',
'entity_type' => 'entity_test',
'type' => 'decimal',
));
$field_storage->save();
try {
$field_storage->set('type', 'integer');
$field_storage->save();
$this->fail(t('Cannot update a field to a different type.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot update a field to a different type.'));
}
}
/**
* Test updating a field storage.
*/
function testUpdate() {
// Create a field with a defined cardinality, so that we can ensure it's
// respected. Since cardinality enforcement is consistent across database
// systems, it makes a good test case.
$cardinality = 4;
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_update',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => $cardinality,
));
$field_storage->save();
$field = entity_create('field_config', array(
'field_storage' => $field_storage,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
));
$field->save();
do {
$entity = entity_create('entity_test');
// Fill in the entity with more values than $cardinality.
for ($i = 0; $i < 20; $i++) {
// We can not use $i here because 0 values are filtered out.
$entity->field_update[] = $i + 1;
}
// Load back and assert there are $cardinality number of values.
$entity = $this->entitySaveReload($entity);
$this->assertEqual(count($entity->field_update), $field_storage->getCardinality());
// Now check the values themselves.
for ($delta = 0; $delta < $cardinality; $delta++) {
$this->assertEqual($entity->field_update[$delta]->value, $delta + 1);
}
// Increase $cardinality and set the field cardinality to the new value.
$field_storage->setCardinality(++$cardinality);
$field_storage->save();
} while ($cardinality < 6);
}
/**
* Test field type modules forbidding an update.
*/
function testUpdateForbid() {
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'forbidden',
'entity_type' => 'entity_test',
'type' => 'test_field',
'settings' => array(
'changeable' => 0,
'unchangeable' => 0
)));
$field_storage->save();
$field_storage->setSetting('changeable', $field_storage->getSetting('changeable') + 1);
try {
$field_storage->save();
$this->pass(t("A changeable setting can be updated."));
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->fail(t("An unchangeable setting cannot be updated."));
}
$field_storage->setSetting('unchangeable', $field_storage->getSetting('unchangeable') + 1);
try {
$field_storage->save();
$this->fail(t("An unchangeable setting can be updated."));
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass(t("An unchangeable setting cannot be updated."));
}
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldTestBase.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\WebTestBase;
/**
* Parent class for Field API tests.
*/
abstract class FieldTestBase extends WebTestBase {
/**
* Generate random values for a field_test field.
*
* @param $cardinality
* Number of values to generate.
* @return
* An array of random values, in the format expected for field values.
*/
function _generateTestFieldValues($cardinality) {
$values = array();
for ($i = 0; $i < $cardinality; $i++) {
// field_test fields treat 0 as 'empty value'.
$values[$i]['value'] = mt_rand(1, 127);
}
return $values;
}
/**
* Assert that a field has the expected values in an entity.
*
* This function only checks a single column in the field values.
*
* @param EntityInterface $entity
* The entity to test.
* @param $field_name
* The name of the field to test
* @param $expected_values
* The array of expected values.
* @param $langcode
* (Optional) The language code for the values. Defaults to
* \Drupal\Core\Language\LanguageInterface::LANGCODE_NOT_SPECIFIED.
* @param $column
* (Optional) The name of the column to check. Defaults to 'value'.
*/
function assertFieldValues(EntityInterface $entity, $field_name, $expected_values, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED, $column = 'value') {
// Re-load the entity to make sure we have the latest changes.
\Drupal::entityManager()->getStorage($entity->getEntityTypeId())->resetCache(array($entity->id()));
$e = entity_load($entity->getEntityTypeId(), $entity->id());
$field = $values = $e->getTranslation($langcode)->$field_name;
// Filter out empty values so that they don't mess with the assertions.
$field->filterEmptyItems();
$values = $field->getValue();
$this->assertEqual(count($values), count($expected_values), 'Expected number of values were saved.');
foreach ($expected_values as $key => $value) {
$this->assertEqual($values[$key][$column], $value, format_string('Value @value was saved correctly.', array('@value' => $value)));
}
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldTypePluginManagerTest.
*/
namespace Drupal\field\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\entity_test\Entity\EntityTest;
/**
* Tests the field type manager.
*
* @group field
*/
class FieldTypePluginManagerTest extends FieldUnitTestBase {
/**
* Tests the default settings convenience methods.
*/
function testDefaultSettings() {
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
foreach (array('test_field', 'shape', 'hidden_test_field') as $type) {
$definition = $field_type_manager->getDefinition($type);
$this->assertIdentical($field_type_manager->getDefaultStorageSettings($type), $definition['class']::defaultStorageSettings(), format_string("%type storage settings were returned", array('%type' => $type)));
$this->assertIdentical($field_type_manager->getDefaultFieldSettings($type), $definition['class']::defaultFieldSettings(), format_string(" %type field settings were returned", array('%type' => $type)));
}
}
/**
* Tests creation of field item instances.
*/
public function testCreateInstance() {
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
foreach (array('test_field', 'shape', 'hidden_test_field') as $type) {
$definition = $field_type_manager->getDefinition($type);
$class = $definition['class'];
$field_name = 'field_' . $type;
$field_definition = BaseFieldDefinition::create($type);
$configuration = array(
'field_definition' => $field_definition,
'name' => $field_name,
'parent' => NULL,
);
$instance = $field_type_manager->createInstance($type, $configuration);
$this->assertTrue($instance instanceof $class, SafeMarkup::format('Created a @class instance', array('@class' => $class)));
$this->assertEqual($field_name, $instance->getName(), SafeMarkup::format('Instance name is @name', array('@name' => $field_name)));
}
}
/**
* Tests creation of field item instances.
*/
public function testCreateInstanceWithConfig() {
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$type = 'test_field';
$definition = $field_type_manager->getDefinition($type);
$class = $definition['class'];
$field_name = 'field_' . $type;
$field_definition = BaseFieldDefinition::create($type)
->setLabel('Jenny')
->setDefaultValue(8675309);
$configuration = array(
'field_definition' => $field_definition,
'name' => $field_name,
'parent' => NULL,
);
$entity = EntityTest::create();
$instance = $field_type_manager->createInstance($type, $configuration);
$this->assertTrue($instance instanceof $class, SafeMarkup::format('Created a @class instance', array('@class' => $class)));
$this->assertEqual($field_name, $instance->getName(), SafeMarkup::format('Instance name is @name', array('@name' => $field_name)));
$this->assertEqual($instance->getFieldDefinition()->getLabel(), 'Jenny', 'Instance label is Jenny');
$this->assertEqual($instance->getFieldDefinition()->getDefaultValue($entity), [['value' => 8675309]], 'Instance default_value is 8675309');
}
}

View file

@ -0,0 +1,206 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldUnitTestBase.
*/
namespace Drupal\field\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\KernelTestBase;
/**
* Parent class for Field API unit tests.
*/
abstract class FieldUnitTestBase extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('user', 'system', 'field', 'text', 'entity_test', 'field_test', 'entity_reference');
/**
* Bag of created field storages and fields.
*
* Allows easy access to test field storage/field names/IDs/objects via:
* - $this->fieldTestData->field_name[suffix]
* - $this->fieldTestData->field_storage[suffix]
* - $this->fieldTestData->field_storage_uuid[suffix]
* - $this->fieldTestData->field[suffix]
* - $this->fieldTestData->field_definition[suffix]
*
* @see \Drupal\field\Tests\FieldUnitTestBase::createFieldWithStorage()
*
* @var \ArrayObject
*/
protected $fieldTestData;
/**
* Set the default field storage backend for fields created during tests.
*/
protected function setUp() {
parent::setUp();
$this->fieldTestData = new \ArrayObject(array(), \ArrayObject::ARRAY_AS_PROPS);
$this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$this->installSchema('system', ['router', 'sequences', 'key_value']);
// Set default storage backend and configure the theme system.
$this->installConfig(array('field', 'system'));
// Create user 1.
$storage = \Drupal::entityManager()->getStorage('user');
$storage
->create(array(
'uid' => 1,
'name' => 'entity-test',
'mail' => 'entity@localhost',
'status' => TRUE,
))
->save();
}
/**
* Create a field and an associated field storage.
*
* @param string $suffix
* (optional) A string that should only contain characters that are valid in
* PHP variable names as well.
* @param string $entity_type
* (optional) The entity type on which the field should be created.
* Defaults to "entity_test".
* @param string $bundle
* (optional) The entity type on which the field should be created.
* Defaults to the default bundle of the entity type.
*/
protected function createFieldWithStorage($suffix = '', $entity_type = 'entity_test', $bundle = NULL) {
if (empty($bundle)) {
$bundle = $entity_type;
}
$field_name = 'field_name' . $suffix;
$field_storage = 'field_storage' . $suffix;
$field_storage_uuid = 'field_storage_uuid' . $suffix;
$field = 'field' . $suffix;
$field_definition = 'field_definition' . $suffix;
$this->fieldTestData->$field_name = Unicode::strtolower($this->randomMachineName() . '_field_name' . $suffix);
$this->fieldTestData->$field_storage = entity_create('field_storage_config', array(
'field_name' => $this->fieldTestData->$field_name,
'entity_type' => $entity_type,
'type' => 'test_field',
'cardinality' => 4,
));
$this->fieldTestData->$field_storage->save();
$this->fieldTestData->$field_storage_uuid = $this->fieldTestData->$field_storage->uuid();
$this->fieldTestData->$field_definition = array(
'field_storage' => $this->fieldTestData->$field_storage,
'bundle' => $bundle,
'label' => $this->randomMachineName() . '_label',
'description' => $this->randomMachineName() . '_description',
'settings' => array(
'test_field_setting' => $this->randomMachineName(),
),
);
$this->fieldTestData->$field = entity_create('field_config', $this->fieldTestData->$field_definition);
$this->fieldTestData->$field->save();
entity_get_form_display($entity_type, $bundle, 'default')
->setComponent($this->fieldTestData->$field_name, array(
'type' => 'test_field_widget',
'settings' => array(
'test_widget_setting' => $this->randomMachineName(),
)
))
->save();
}
/**
* Saves and reloads an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to save.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity, freshly reloaded from storage.
*/
protected function entitySaveReload(EntityInterface $entity) {
$entity->save();
$controller = $this->container->get('entity.manager')->getStorage($entity->getEntityTypeId());
$controller->resetCache();
return $controller->load($entity->id());
}
/**
* Validate and save entity. Fail if violations are found.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to save.
*
* @return void
*/
protected function entityValidateAndSave(EntityInterface $entity) {
$violations = $entity->validate();
if ($violations->count()) {
$this->fail($violations);
}
else {
$entity->save();
}
}
/**
* Generate random values for a field_test field.
*
* @param $cardinality
* Number of values to generate.
* @return
* An array of random values, in the format expected for field values.
*/
protected function _generateTestFieldValues($cardinality) {
$values = array();
for ($i = 0; $i < $cardinality; $i++) {
// field_test fields treat 0 as 'empty value'.
$values[$i]['value'] = mt_rand(1, 127);
}
return $values;
}
/**
* Assert that a field has the expected values in an entity.
*
* This function only checks a single column in the field values.
*
* @param EntityInterface $entity
* The entity to test.
* @param $field_name
* The name of the field to test
* @param $expected_values
* The array of expected values.
* @param $langcode
* (Optional) The language code for the values. Defaults to
* \Drupal\Core\Language\LanguageInterface::LANGCODE_NOT_SPECIFIED.
* @param $column
* (Optional) The name of the column to check. Defaults to 'value'.
*/
protected function assertFieldValues(EntityInterface $entity, $field_name, $expected_values, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED, $column = 'value') {
// Re-load the entity to make sure we have the latest changes.
\Drupal::entityManager()->getStorage($entity->getEntityTypeId())->resetCache(array($entity->id()));
$e = entity_load($entity->getEntityTypeId(), $entity->id());
$field = $values = $e->getTranslation($langcode)->$field_name;
// Filter out empty values so that they don't mess with the assertions.
$field->filterEmptyItems();
$values = $field->getValue();
$this->assertEqual(count($values), count($expected_values), 'Expected number of values were saved.');
foreach ($expected_values as $key => $value) {
$this->assertEqual($values[$key][$column], $value, format_string('Value @value was saved correctly.', array('@value' => $value)));
}
}
}

View file

@ -0,0 +1,102 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FieldValidationTest.
*/
namespace Drupal\field\Tests;
/**
* Tests field validation.
*
* @group field
*/
class FieldValidationTest extends FieldUnitTestBase {
/**
* @var string
*/
private $entityType;
/**
* @var string
*/
private $bundle;
/**
* @var \Drupal\Core\Entity\EntityInterface
*/
private $entity;
protected function setUp() {
parent::setUp();
// Create a field and storage of type 'test_field', on the 'entity_test'
// entity type.
$this->entityType = 'entity_test';
$this->bundle = 'entity_test';
$this->createFieldWithStorage('', $this->entityType, $this->bundle);
// Create an 'entity_test' entity.
$this->entity = entity_create($this->entityType, array(
'type' => $this->bundle,
));
}
/**
* Tests that the number of values is validated against the field cardinality.
*/
function testCardinalityConstraint() {
$cardinality = $this->fieldTestData->field_storage->getCardinality();
$entity = $this->entity;
for ($delta = 0; $delta < $cardinality + 1; $delta++) {
$entity->{$this->fieldTestData->field_name}[] = array('value' => 1);
}
// Validate the field.
$violations = $entity->{$this->fieldTestData->field_name}->validate();
// Check that the expected constraint violations are reported.
$this->assertEqual(count($violations), 1);
$this->assertEqual($violations[0]->getPropertyPath(), '');
$this->assertEqual($violations[0]->getMessage(), t('%name: this field cannot hold more than @count values.', array('%name' => $this->fieldTestData->field->getLabel(), '@count' => $cardinality)));
}
/**
* Tests that constraints defined by the field type are validated.
*/
function testFieldConstraints() {
$cardinality = $this->fieldTestData->field_storage->getCardinality();
$entity = $this->entity;
// The test is only valid if the field cardinality is greater than 2.
$this->assertTrue($cardinality >= 2);
// Set up values for the field.
$expected_violations = array();
for ($delta = 0; $delta < $cardinality; $delta++) {
// All deltas except '1' have incorrect values.
if ($delta == 1) {
$value = 1;
}
else {
$value = -1;
$expected_violations[$delta . '.value'][] = t('%name does not accept the value -1.', array('%name' => $this->fieldTestData->field->getLabel()));
}
$entity->{$this->fieldTestData->field_name}[] = $value;
}
// Validate the field.
$violations = $entity->{$this->fieldTestData->field_name}->validate();
// Check that the expected constraint violations are reported.
$violations_by_path = array();
foreach ($violations as $violation) {
$violations_by_path[$violation->getPropertyPath()][] = $violation->getMessage();
}
$this->assertEqual($violations_by_path, $expected_violations);
}
}

View file

@ -0,0 +1,652 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FormTest.
*/
namespace Drupal\field\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormState;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests field form handling.
*
* @group field
*/
class FormTest extends FieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'field_test', 'options', 'entity_test');
/**
* An array of values defining a field single.
*
* @var array
*/
protected $fieldStorageSingle;
/**
* An array of values defining a field multiple.
*
* @var array
*/
protected $fieldStorageMultiple;
/**
* An array of values defining a field with unlimited cardinality.
*
* @var array
*/
protected $fieldStorageUnlimited;
/**
* An array of values defining a field.
*
* @var array
*/
protected $field;
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('view test entity', 'administer entity_test content'));
$this->drupalLogin($web_user);
$this->fieldStorageSingle = array(
'field_name' => 'field_single',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
$this->fieldStorageMultiple = array(
'field_name' => 'field_multiple',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => 4,
);
$this->fieldStorageUnlimited = array(
'field_name' => 'field_unlimited',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
);
$this->field = array(
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'label' => $this->randomMachineName() . '_label',
'description' => '[site:name]_description',
'weight' => mt_rand(0, 127),
'settings' => array(
'test_field_setting' => $this->randomMachineName(),
),
);
}
function testFieldFormSingle() {
$field_storage = $this->fieldStorageSingle;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name)
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
// Create token value expected for description.
$token_description = SafeMarkup::checkPlain($this->config('system.site')->get('name')) . '_description';
$this->assertText($token_description, 'Token replacement for description is displayed');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertNoField("{$field_name}[1][value]", 'No extraneous widget is displayed');
// Check that hook_field_widget_form_alter() does not believe this is the
// default value form.
$this->assertNoText('From hook_field_widget_form_alter(): Default form is true.', 'Not default value form in hook_field_widget_form_alter().');
// Submit with invalid value (field-level validation).
$edit = array(
"{$field_name}[0][value]" => -1
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name does not accept the value -1.', array('%name' => $this->field['label'])), 'Field validation fails with invalid input.');
// TODO : check that the correct field is flagged for error.
// Create an entity
$value = mt_rand(1, 127);
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->{$field_name}->value, $value, 'Field value was saved');
// Display edit form.
$this->drupalGet('entity_test/manage/' . $id);
$this->assertFieldByName("{$field_name}[0][value]", $value, 'Widget is displayed with the correct default value');
$this->assertNoField("{$field_name}[1][value]", 'No extraneous widget is displayed');
// Update the entity.
$value = mt_rand(1, 127);
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(t('entity_test @id has been updated.', array('@id' => $id)), 'Entity was updated');
$this->container->get('entity.manager')->getStorage('entity_test')->resetCache(array($id));
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->{$field_name}->value, $value, 'Field value was updated');
// Empty the field.
$value = '';
$edit = array(
"{$field_name}[0][value]" => $value
);
$this->drupalPostForm('entity_test/manage/' . $id, $edit, t('Save'));
$this->assertText(t('entity_test @id has been updated.', array('@id' => $id)), 'Entity was updated');
$this->container->get('entity.manager')->getStorage('entity_test')->resetCache(array($id));
$entity = entity_load('entity_test', $id);
$this->assertTrue($entity->{$field_name}->isEmpty(), 'Field was emptied');
}
/**
* Tests field widget default values on entity forms.
*/
function testFieldFormDefaultValue() {
$field_storage = $this->fieldStorageSingle;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
$default = rand(1, 127);
$this->field['default_value'] = array(array('value' => $default));
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name)
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
// Test that the default value is displayed correctly.
$this->assertFieldByXpath("//input[@name='{$field_name}[0][value]' and @value='$default']");
// Try to submit an empty value.
$edit = array(
"{$field_name}[0][value]" => '',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created.');
$entity = entity_load('entity_test', $id);
$this->assertTrue($entity->{$field_name}->isEmpty(), 'Field is now empty.');
}
function testFieldFormSingleRequired() {
$field_storage = $this->fieldStorageSingle;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
$this->field['required'] = TRUE;
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name)
->save();
// Submit with missing required value.
$edit = array();
$this->drupalPostForm('entity_test/add', $edit, t('Save'));
$this->assertRaw(t('!name field is required.', array('!name' => $this->field['label'])), 'Required field with no value fails validation');
// Create an entity
$value = mt_rand(1, 127);
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->{$field_name}->value, $value, 'Field value was saved');
// Edit with missing required value.
$value = '';
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm('entity_test/manage/' . $id, $edit, t('Save'));
$this->assertRaw(t('!name field is required.', array('!name' => $this->field['label'])), 'Required field with no value fails validation');
}
// function testFieldFormMultiple() {
// $this->field = $this->field_multiple;
// $field_name = $this->field['field_name'];
// $this->instance['field_name'] = $field_name;
// entity_create('field_storage_config', $this->field)->save();
// entity_create('field_config', $this->instance)->save();
// }
function testFieldFormUnlimited() {
$field_storage = $this->fieldStorageUnlimited;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name)
->save();
// Display creation form -> 1 widget.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget 1 is displayed');
$this->assertNoField("{$field_name}[1][value]", 'No extraneous widget is displayed');
// Press 'add more' button -> 2 widgets.
$this->drupalPostForm(NULL, array(), t('Add another item'));
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget 1 is displayed');
$this->assertFieldByName("{$field_name}[1][value]", '', 'New widget is displayed');
$this->assertNoField("{$field_name}[2][value]", 'No extraneous widget is displayed');
// TODO : check that non-field inputs are preserved ('title'), etc.
// Yet another time so that we can play with more values -> 3 widgets.
$this->drupalPostForm(NULL, array(), t('Add another item'));
// Prepare values and weights.
$count = 3;
$delta_range = $count - 1;
$values = $weights = $pattern = $expected_values = array();
$edit = array();
for ($delta = 0; $delta <= $delta_range; $delta++) {
// Assign unique random values and weights.
do {
$value = mt_rand(1, 127);
} while (in_array($value, $values));
do {
$weight = mt_rand(-$delta_range, $delta_range);
} while (in_array($weight, $weights));
$edit["{$field_name}[$delta][value]"] = $value;
$edit["{$field_name}[$delta][_weight]"] = $weight;
// We'll need three slightly different formats to check the values.
$values[$delta] = $value;
$weights[$delta] = $weight;
$field_values[$weight]['value'] = (string) $value;
$pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*";
}
// Press 'add more' button -> 4 widgets
$this->drupalPostForm(NULL, $edit, t('Add another item'));
for ($delta = 0; $delta <= $delta_range; $delta++) {
$this->assertFieldByName("{$field_name}[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
$this->assertFieldByName("{$field_name}[$delta][_weight]", $weights[$delta], "Widget $delta has the right weight");
}
ksort($pattern);
$pattern = implode('.*', array_values($pattern));
$this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
$this->assertFieldByName("{$field_name}[$delta][value]", '', "New widget is displayed");
$this->assertFieldByName("{$field_name}[$delta][_weight]", $delta, "New widget has the right weight");
$this->assertNoField("{$field_name}[" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
// Submit the form and create the entity.
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
$entity = entity_load('entity_test', $id);
ksort($field_values);
$field_values = array_values($field_values);
$this->assertIdentical($entity->{$field_name}->getValue(), $field_values, 'Field values were saved in the correct order');
// Display edit form: check that the expected number of widgets is
// displayed, with correct values change values, reorder, leave an empty
// value in the middle.
// Submit: check that the entity is updated with correct values
// Re-submit: check that the field can be emptied.
// Test with several multiple fields in a form
}
/**
* Tests the position of the required label.
*/
public function testFieldFormUnlimitedRequired() {
$field_name = $this->fieldStorageUnlimited['field_name'];
$this->field['field_name'] = $field_name;
$this->field['required'] = TRUE;
FieldStorageConfig::create($this->fieldStorageUnlimited)->save();
FieldConfig::create($this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name)
->save();
// Display creation form -> 1 widget.
$this->drupalGet('entity_test/add');
// Check that the Required symbol is present for the multifield label.
$this->assertRaw(SafeMarkup::format('<h4 class="label form-required">@label</h4>', array('@label' => $this->field['label'])),
'Required symbol added field label.');
// Check that the label of the field input is visually hidden and contains
// the field title and an indication of the delta for a11y.
$this->assertRaw(SafeMarkup::format('<label for="edit-field-unlimited-0-value" class="visually-hidden form-required">@label (value 1)</label>', array('@label' => $this->field['label'])),
'Required symbol not added for field input.');
}
/**
* Tests widget handling of multiple required radios.
*/
function testFieldFormMultivalueWithRequiredRadio() {
// Create a multivalue test field.
$field_storage = $this->fieldStorageUnlimited;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name)
->save();
// Add a required radio field.
entity_create('field_storage_config', array(
'field_name' => 'required_radio_test',
'entity_type' => 'entity_test',
'type' => 'list_string',
'settings' => array(
'allowed_values' => array('yes' => 'yes', 'no' => 'no'),
),
))->save();
$field = array(
'field_name' => 'required_radio_test',
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'required' => TRUE,
);
entity_create('field_config', $field)->save();
entity_get_form_display($field['entity_type'], $field['bundle'], 'default')
->setComponent($field['field_name'], array(
'type' => 'options_buttons',
))
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
// Press the 'Add more' button.
$this->drupalPostForm(NULL, array(), t('Add another item'));
// Verify that no error is thrown by the radio element.
$this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed.');
// Verify that the widget is added.
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget 1 is displayed');
$this->assertFieldByName("{$field_name}[1][value]", '', 'New widget is displayed');
$this->assertNoField("{$field_name}[2][value]", 'No extraneous widget is displayed');
}
function testFieldFormJSAddMore() {
$field_storage = $this->fieldStorageUnlimited;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name)
->save();
// Display creation form -> 1 widget.
$this->drupalGet('entity_test/add');
// Press 'add more' button a couple times -> 3 widgets.
// drupalPostAjaxForm() will not work iteratively, so we add those through
// non-JS submission.
$this->drupalPostForm(NULL, array(), t('Add another item'));
$this->drupalPostForm(NULL, array(), t('Add another item'));
// Prepare values and weights.
$count = 3;
$delta_range = $count - 1;
$values = $weights = $pattern = $expected_values = $edit = array();
for ($delta = 0; $delta <= $delta_range; $delta++) {
// Assign unique random values and weights.
do {
$value = mt_rand(1, 127);
} while (in_array($value, $values));
do {
$weight = mt_rand(-$delta_range, $delta_range);
} while (in_array($weight, $weights));
$edit["{$field_name}[$delta][value]"] = $value;
$edit["{$field_name}[$delta][_weight]"] = $weight;
// We'll need three slightly different formats to check the values.
$values[$delta] = $value;
$weights[$delta] = $weight;
$field_values[$weight]['value'] = (string) $value;
$pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*";
}
// Press 'add more' button through Ajax, and place the expected HTML result
// as the tested content.
$commands = $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_add_more');
$this->setRawContent($commands[2]['data']);
for ($delta = 0; $delta <= $delta_range; $delta++) {
$this->assertFieldByName("{$field_name}[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
$this->assertFieldByName("{$field_name}[$delta][_weight]", $weights[$delta], "Widget $delta has the right weight");
}
ksort($pattern);
$pattern = implode('.*', array_values($pattern));
$this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
$this->assertFieldByName("{$field_name}[$delta][value]", '', "New widget is displayed");
$this->assertFieldByName("{$field_name}[$delta][_weight]", $delta, "New widget has the right weight");
$this->assertNoField("{$field_name}[" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
}
/**
* Tests widgets handling multiple values.
*/
function testFieldFormMultipleWidget() {
// Create a field with fixed cardinality, configure the form to use a
// "multiple" widget.
$field_storage = $this->fieldStorageMultiple;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($field_name, array(
'type' => 'test_field_widget_multiple',
))
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName($field_name, '', 'Widget is displayed.');
// Create entity with three values.
$edit = array(
$field_name => '1, 2, 3',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
// Check that the values were saved.
$entity_init = entity_load('entity_test', $id);
$this->assertFieldValues($entity_init, $field_name, array(1, 2, 3));
// Display the form, check that the values are correctly filled in.
$this->drupalGet('entity_test/manage/' . $id);
$this->assertFieldByName($field_name, '1, 2, 3', 'Widget is displayed.');
// Submit the form with more values than the field accepts.
$edit = array($field_name => '1, 2, 3, 4, 5');
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw('this field cannot hold more than 4 values', 'Form validation failed.');
// Check that the field values were not submitted.
$this->assertFieldValues($entity_init, $field_name, array(1, 2, 3));
}
/**
* Tests fields with no 'edit' access.
*/
function testFieldFormAccess() {
$entity_type = 'entity_test_rev';
// Create a "regular" field.
$field_storage = $this->fieldStorageSingle;
$field_storage['entity_type'] = $entity_type;
$field_name = $field_storage['field_name'];
$field = $this->field;
$field['field_name'] = $field_name;
$field['entity_type'] = $entity_type;
$field['bundle'] = $entity_type;
entity_create('field_storage_config', $field_storage)->save();
entity_create('field_config', $field)->save();
entity_get_form_display($entity_type, $entity_type, 'default')
->setComponent($field_name)
->save();
// Create a field with no edit access. See
// field_test_entity_field_access().
$field_storage_no_access = array(
'field_name' => 'field_no_edit_access',
'entity_type' => $entity_type,
'type' => 'test_field',
);
$field_name_no_access = $field_storage_no_access['field_name'];
$field_no_access = array(
'field_name' => $field_name_no_access,
'entity_type' => $entity_type,
'bundle' => $entity_type,
'default_value' => array(0 => array('value' => 99)),
);
entity_create('field_storage_config', $field_storage_no_access)->save();
entity_create('field_config', $field_no_access)->save();
entity_get_form_display($field_no_access['entity_type'], $field_no_access['bundle'], 'default')
->setComponent($field_name_no_access)
->save();
// Test that the form structure includes full information for each delta
// apart from #access.
$entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0));
$display = entity_get_form_display($entity_type, $entity_type, 'default');
$form = array();
$form_state = new FormState();
$display->buildForm($entity, $form, $form_state);
$this->assertFalse($form[$field_name_no_access]['#access'], 'Field #access is FALSE for the field without edit access.');
// Display creation form.
$this->drupalGet($entity_type . '/add');
$this->assertNoFieldByName("{$field_name_no_access}[0][value]", '', 'Widget is not displayed if field access is denied.');
// Create entity.
$edit = array(
"{$field_name}[0][value]" => 1,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match("|$entity_type/manage/(\d+)|", $this->url, $match);
$id = $match[1];
// Check that the default value was saved.
$entity = entity_load($entity_type, $id);
$this->assertEqual($entity->$field_name_no_access->value, 99, 'Default value was saved for the field with no edit access.');
$this->assertEqual($entity->$field_name->value, 1, 'Entered value vas saved for the field with edit access.');
// Create a new revision.
$edit = array(
"{$field_name}[0][value]" => 2,
'revision' => TRUE,
);
$this->drupalPostForm($entity_type . '/manage/' . $id, $edit, t('Save'));
// Check that the new revision has the expected values.
$this->container->get('entity.manager')->getStorage($entity_type)->resetCache(array($id));
$entity = entity_load($entity_type, $id);
$this->assertEqual($entity->$field_name_no_access->value, 99, 'New revision has the expected value for the field with no edit access.');
$this->assertEqual($entity->$field_name->value, 2, 'New revision has the expected value for the field with edit access.');
// Check that the revision is also saved in the revisions table.
// $entity = entity_revision_load($entity_type, $entity->getRevisionId());
$this->assertEqual($entity->$field_name_no_access->value, 99, 'New revision has the expected value for the field with no edit access.');
$this->assertEqual($entity->$field_name->value, 2, 'New revision has the expected value for the field with edit access.');
}
/**
* Tests hiding a field in a form.
*/
function testHiddenField() {
$entity_type = 'entity_test_rev';
$field_storage = $this->fieldStorageSingle;
$field_storage['entity_type'] = $entity_type;
$field_name = $field_storage['field_name'];
$this->field['field_name'] = $field_name;
$this->field['default_value'] = array(0 => array('value' => 99));
$this->field['entity_type'] = $entity_type;
$this->field['bundle'] = $entity_type;
entity_create('field_storage_config', $field_storage)->save();
$this->field = entity_create('field_config', $this->field);
$this->field->save();
// We explicitly do not assign a widget in a form display, so the field
// stays hidden in forms.
// Display the entity creation form.
$this->drupalGet($entity_type . '/add');
// Create an entity and test that the default value is assigned correctly to
// the field that uses the hidden widget.
$this->assertNoField("{$field_name}[0][value]", 'The field does not appear in the form');
$this->drupalPostForm(NULL, array(), t('Save'));
preg_match('|' . $entity_type . '/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test_rev @id has been created.', array('@id' => $id)), 'Entity was created');
$entity = entity_load($entity_type, $id);
$this->assertEqual($entity->{$field_name}->value, 99, 'Default value was saved');
// Update the field to remove the default value, and switch to the default
// widget.
$this->field->default_value = array();
$this->field->save();
entity_get_form_display($entity_type, $this->field->getTargetBundle(), 'default')
->setComponent($this->field->getName(), array(
'type' => 'test_field_widget',
))
->save();
// Display edit form.
$this->drupalGet($entity_type . '/manage/' . $id);
$this->assertFieldByName("{$field_name}[0][value]", 99, 'Widget is displayed with the correct default value');
// Update the entity.
$value = mt_rand(1, 127);
$edit = array("{$field_name}[0][value]" => $value);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(t('entity_test_rev @id has been updated.', array('@id' => $id)), 'Entity was updated');
\Drupal::entityManager()->getStorage($entity_type)->resetCache(array($id));
$entity = entity_load($entity_type, $id);
$this->assertEqual($entity->{$field_name}->value, $value, 'Field value was updated');
// Set the field back to hidden.
entity_get_form_display($entity_type, $this->field->getTargetBundle(), 'default')
->removeComponent($this->field->getName())
->save();
// Create a new revision.
$edit = array('revision' => TRUE);
$this->drupalPostForm($entity_type . '/manage/' . $id, $edit, t('Save'));
// Check that the expected value has been carried over to the new revision.
\Drupal::entityManager()->getStorage($entity_type)->resetCache(array($id));
$entity = entity_load($entity_type, $id);
$this->assertEqual($entity->{$field_name}->value, $value, 'New revision has the expected value for the field with the Hidden widget');
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\FormatterPluginManagerTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FormatterPluginManager;
/**
* Tests the field formatter plugin manager.
*
* @group field
*/
class FormatterPluginManagerTest extends FieldUnitTestBase {
/**
* Tests that getInstance falls back on default if current is not applicable.
*
* @see \Drupal\field\Tests\WidgetPluginManagerTest::testNotApplicableFallback()
*/
public function testNotApplicableFallback() {
/** @var FormatterPluginManager $formatter_plugin_manager */
$formatter_plugin_manager = \Drupal::service('plugin.manager.field.formatter');
$base_field_definition = BaseFieldDefinition::create('test_field')
// Set a name that will make isApplicable() return TRUE.
->setName('field_test_field');
$formatter_options = array(
'field_definition' => $base_field_definition,
'view_mode' => 'default',
'configuration' => array(
'type' => 'field_test_applicable',
),
);
$instance = $formatter_plugin_manager->getInstance($formatter_options);
$this->assertEqual($instance->getPluginId(), 'field_test_applicable');
// Now set name to something that makes isApplicable() return FALSE.
$base_field_definition->setName('deny_applicable');
$instance = $formatter_plugin_manager->getInstance($formatter_options);
// Instance should be default widget.
$this->assertNotEqual($instance->getPluginId(), 'field_test_applicable');
$this->assertEqual($instance->getPluginId(), 'field_test_default');
}
}

View file

@ -0,0 +1,168 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\NestedFormTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Tests field elements in nested forms.
*
* @group field
*/
class NestedFormTest extends FieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field_test', 'entity_test');
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('view test entity', 'administer entity_test content'));
$this->drupalLogin($web_user);
$this->fieldStorageSingle = array(
'field_name' => 'field_single',
'entity_type' => 'entity_test',
'type' => 'test_field',
);
$this->fieldStorageUnlimited = array(
'field_name' => 'field_unlimited',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
);
$this->field = array(
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'label' => $this->randomMachineName() . '_label',
'description' => '[site:name]_description',
'weight' => mt_rand(0, 127),
'settings' => array(
'test_field_setting' => $this->randomMachineName(),
),
);
}
/**
* Tests Field API form integration within a subform.
*/
function testNestedFieldForm() {
// Add two fields on the 'entity_test'
entity_create('field_storage_config', $this->fieldStorageSingle)->save();
entity_create('field_storage_config', $this->fieldStorageUnlimited)->save();
$this->field['field_name'] = 'field_single';
$this->field['label'] = 'Single field';
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($this->field['field_name'])
->save();
$this->field['field_name'] = 'field_unlimited';
$this->field['label'] = 'Unlimited field';
entity_create('field_config', $this->field)->save();
entity_get_form_display($this->field['entity_type'], $this->field['bundle'], 'default')
->setComponent($this->field['field_name'])
->save();
// Create two entities.
$entity_type = 'entity_test';
$entity_1 = entity_create($entity_type, array('id' => 1));
$entity_1->enforceIsNew();
$entity_1->field_single->value = 0;
$entity_1->field_unlimited->value = 1;
$entity_1->save();
$entity_2 = entity_create($entity_type, array('id' => 2));
$entity_2->enforceIsNew();
$entity_2->field_single->value = 10;
$entity_2->field_unlimited->value = 11;
$entity_2->save();
// Display the 'combined form'.
$this->drupalGet('test-entity/nested/1/2');
$this->assertFieldByName('field_single[0][value]', 0, 'Entity 1: field_single value appears correctly is the form.');
$this->assertFieldByName('field_unlimited[0][value]', 1, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
$this->assertFieldByName('entity_2[field_single][0][value]', 10, 'Entity 2: field_single value appears correctly is the form.');
$this->assertFieldByName('entity_2[field_unlimited][0][value]', 11, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
// Submit the form and check that the entities are updated accordingly.
$edit = array(
'field_single[0][value]' => 1,
'field_unlimited[0][value]' => 2,
'field_unlimited[1][value]' => 3,
'entity_2[field_single][0][value]' => 11,
'entity_2[field_unlimited][0][value]' => 12,
'entity_2[field_unlimited][1][value]' => 13,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$entity_1 = entity_load($entity_type, 1);
$entity_2 = entity_load($entity_type, 2);
$this->assertFieldValues($entity_1, 'field_single', array(1));
$this->assertFieldValues($entity_1, 'field_unlimited', array(2, 3));
$this->assertFieldValues($entity_2, 'field_single', array(11));
$this->assertFieldValues($entity_2, 'field_unlimited', array(12, 13));
// Submit invalid values and check that errors are reported on the
// correct widgets.
$edit = array(
'field_unlimited[1][value]' => -1,
);
$this->drupalPostForm('test-entity/nested/1/2', $edit, t('Save'));
$this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 1: the field validation error was reported.');
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-field-unlimited-1-value'));
$this->assertTrue($error_field, 'Entity 1: the error was flagged on the correct element.');
$edit = array(
'entity_2[field_unlimited][1][value]' => -1,
);
$this->drupalPostForm('test-entity/nested/1/2', $edit, t('Save'));
$this->assertRaw(t('%label does not accept the value -1', array('%label' => 'Unlimited field')), 'Entity 2: the field validation error was reported.');
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', array(':id' => 'edit-entity-2-field-unlimited-1-value'));
$this->assertTrue($error_field, 'Entity 2: the error was flagged on the correct element.');
// Test that reordering works on both entities.
$edit = array(
'field_unlimited[0][_weight]' => 0,
'field_unlimited[1][_weight]' => -1,
'entity_2[field_unlimited][0][_weight]' => 0,
'entity_2[field_unlimited][1][_weight]' => -1,
);
$this->drupalPostForm('test-entity/nested/1/2', $edit, t('Save'));
$this->assertFieldValues($entity_1, 'field_unlimited', array(3, 2));
$this->assertFieldValues($entity_2, 'field_unlimited', array(13, 12));
// Test the 'add more' buttons. Only Ajax submission is tested, because
// the two 'add more' buttons present in the form have the same #value,
// which confuses drupalPostForm().
// 'Add more' button in the first entity:
$this->drupalGet('test-entity/nested/1/2');
$this->drupalPostAjaxForm(NULL, array(), 'field_unlimited_add_more');
$this->assertFieldByName('field_unlimited[0][value]', 3, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
$this->assertFieldByName('field_unlimited[1][value]', 2, 'Entity 1: field_unlimited value 1 appears correctly is the form.');
$this->assertFieldByName('field_unlimited[2][value]', '', 'Entity 1: field_unlimited value 2 appears correctly is the form.');
$this->assertFieldByName('field_unlimited[3][value]', '', 'Entity 1: an empty widget was added for field_unlimited value 3.');
// 'Add more' button in the first entity (changing field values):
$edit = array(
'entity_2[field_unlimited][0][value]' => 13,
'entity_2[field_unlimited][1][value]' => 14,
'entity_2[field_unlimited][2][value]' => 15,
);
$this->drupalPostAjaxForm(NULL, $edit, 'entity_2_field_unlimited_add_more');
$this->assertFieldByName('entity_2[field_unlimited][0][value]', 13, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
$this->assertFieldByName('entity_2[field_unlimited][1][value]', 14, 'Entity 2: field_unlimited value 1 appears correctly is the form.');
$this->assertFieldByName('entity_2[field_unlimited][2][value]', 15, 'Entity 2: field_unlimited value 2 appears correctly is the form.');
$this->assertFieldByName('entity_2[field_unlimited][3][value]', '', 'Entity 2: an empty widget was added for field_unlimited value 3.');
// Save the form and check values are saved correctly.
$this->drupalPostForm(NULL, array(), t('Save'));
$this->assertFieldValues($entity_1, 'field_unlimited', array(3, 2));
$this->assertFieldValues($entity_2, 'field_unlimited', array(13, 14, 15));
}
}

View file

@ -0,0 +1,530 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Number\NumberFieldTest.
*/
namespace Drupal\field\Tests\Number;
use Drupal\Component\Utility\Unicode;
use Drupal\simpletest\WebTestBase;
/**
* Tests the creation of numeric fields.
*
* @group field
*/
class NumberFieldTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'entity_test', 'field_ui');
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(array(
'view test entity',
'administer entity_test content',
'administer content types',
'administer node fields',
'administer node display',
'bypass node access',
'administer entity_test fields',
)));
}
/**
* Test decimal field.
*/
function testNumberDecimalField() {
// Create a field with settings to validate.
$field_name = Unicode::strtolower($this->randomMachineName());
entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'decimal',
'settings' => array(
'precision' => 8, 'scale' => 4,
)
))->save();
entity_create('field_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
))->save();
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'number',
'settings' => array(
'placeholder' => '0.00'
),
))
->save();
entity_get_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'number_decimal',
))
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertRaw('placeholder="0.00"');
// Submit a signed decimal value within the allowed precision and scale.
$value = '-1234.5678';
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
$this->assertRaw($value, 'Value is displayed.');
// Try to create entries with more than one decimal separator; assert fail.
$wrong_entries = array(
'3.14.159',
'0..45469',
'..4589',
'6.459.52',
'6.3..25',
);
foreach ($wrong_entries as $wrong_entry) {
$this->drupalGet('entity_test/add');
$edit = array(
"{$field_name}[0][value]" => $wrong_entry,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', array('%name' => $field_name)), 'Correctly failed to save decimal value with more than one decimal point.');
}
// Try to create entries with minus sign not in the first position.
$wrong_entries = array(
'3-3',
'4-',
'1.3-',
'1.2-4',
'-10-10',
);
foreach ($wrong_entries as $wrong_entry) {
$this->drupalGet('entity_test/add');
$edit = array(
"{$field_name}[0][value]" => $wrong_entry,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', array('%name' => $field_name)), 'Correctly failed to save decimal value with minus sign in the wrong position.');
}
}
/**
* Test integer field.
*/
function testNumberIntegerField() {
$minimum = rand(-4000, -2000);
$maximum = rand(2000, 4000);
// Create a field with settings to validate.
$field_name = Unicode::strtolower($this->randomMachineName());
$storage = entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'integer',
));
$storage->save();
entity_create('field_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'settings' => array(
'min' => $minimum, 'max' => $maximum,
)
))->save();
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'number',
'settings' => array(
'placeholder' => '4'
),
))
->save();
entity_get_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'number_integer',
))
->save();
// Check the storage schema.
$expected = array(
'columns' => array(
'value' => array(
'type' => 'int',
'unsigned' => '',
'size' => 'normal'
),
),
'unique keys' => array(),
'indexes' => array(),
'foreign keys' => array()
);
$this->assertEqual($storage->getSchema(), $expected);
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertRaw('placeholder="4"');
// Submit a valid integer
$value = rand($minimum, $maximum);
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
// Try to set a value below the minimum value
$this->drupalGet('entity_test/add');
$edit = array(
"{$field_name}[0][value]" => $minimum - 1,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be higher than or equal to %minimum.', array('%name' => $field_name, '%minimum' => $minimum)), 'Correctly failed to save integer value less than minimum allowed value.');
// Try to set a decimal value
$this->drupalGet('entity_test/add');
$edit = array(
"{$field_name}[0][value]" => 1.5,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name is not a valid number.', array('%name' => $field_name)), 'Correctly failed to save decimal value to integer field.');
// Try to set a value above the maximum value
$this->drupalGet('entity_test/add');
$edit = array(
"{$field_name}[0][value]" => $maximum + 1,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be lower than or equal to %maximum.', array('%name' => $field_name, '%maximum' => $maximum)), 'Correctly failed to save integer value greater than maximum allowed value.');
// Test with valid entries.
$valid_entries = array(
'-1234',
'0',
'1234',
);
foreach ($valid_entries as $valid_entry) {
$this->drupalGet('entity_test/add');
$edit = array(
"{$field_name}[0][value]" => $valid_entry,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
$this->assertRaw($valid_entry, 'Value is displayed.');
}
}
/**
* Test float field.
*/
function testNumberFloatField() {
// Create a field with settings to validate.
$field_name = Unicode::strtolower($this->randomMachineName());
entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'float',
))->save();
entity_create('field_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
))->save();
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'number',
'settings' => array(
'placeholder' => '0.00'
),
))
->save();
entity_get_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'number_decimal',
))
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertRaw('placeholder="0.00"');
// Submit a signed decimal value within the allowed precision and scale.
$value = '-1234.5678';
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
$this->assertRaw(round($value, 2), 'Value is displayed.');
// Try to create entries with more than one decimal separator; assert fail.
$wrong_entries = array(
'3.14.159',
'0..45469',
'..4589',
'6.459.52',
'6.3..25',
);
foreach ($wrong_entries as $wrong_entry) {
$this->drupalGet('entity_test/add');
$edit = array(
"{$field_name}[0][value]" => $wrong_entry,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', array('%name' => $field_name)), 'Correctly failed to save float value with more than one decimal point.');
}
// Try to create entries with minus sign not in the first position.
$wrong_entries = array(
'3-3',
'4-',
'1.3-',
'1.2-4',
'-10-10',
);
foreach ($wrong_entries as $wrong_entry) {
$this->drupalGet('entity_test/add');
$edit = array(
"{$field_name}[0][value]" => $wrong_entry,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', array('%name' => $field_name)), 'Correctly failed to save float value with minus sign in the wrong position.');
}
}
/**
* Test default formatter behavior
*/
function testNumberFormatter() {
$type = Unicode::strtolower($this->randomMachineName());
$float_field = Unicode::strtolower($this->randomMachineName());
$integer_field = Unicode::strtolower($this->randomMachineName());
$thousand_separators = array('', '.', ',', ' ', chr(8201), "'");
$decimal_separators = array('.', ',');
$prefix = $this->randomMachineName();
$suffix = $this->randomMachineName();
$random_float = rand(0,pow(10,6));
$random_integer = rand(0, pow(10,6));
// Create a content type containing float and integer fields.
$this->drupalCreateContentType(array('type' => $type));
entity_create('field_storage_config', array(
'field_name' => $float_field,
'entity_type' => 'node',
'type' => 'float',
))->save();
entity_create('field_storage_config', array(
'field_name' => $integer_field,
'entity_type' => 'node',
'type' => 'integer',
))->save();
entity_create('field_config', array(
'field_name' => $float_field,
'entity_type' => 'node',
'bundle' => $type,
'settings' => array(
'prefix' => $prefix,
'suffix' => $suffix
),
))->save();
entity_create('field_config', array(
'field_name' => $integer_field,
'entity_type' => 'node',
'bundle' => $type,
'settings' => array(
'prefix' => $prefix,
'suffix' => $suffix
),
))->save();
entity_get_form_display('node', $type, 'default')
->setComponent($float_field, array(
'type' => 'number',
'settings' => array(
'placeholder' => '0.00'
),
))
->setComponent($integer_field, array(
'type' => 'number',
'settings' => array(
'placeholder' => '0.00'
),
))
->save();
entity_get_display('node', $type, 'default')
->setComponent($float_field, array(
'type' => 'number_decimal',
))
->setComponent($integer_field, array(
'type' => 'number_unformatted',
))
->save();
// Create a node to test formatters.
$node = entity_create('node', array(
'type' => $type,
'title' => $this->randomMachineName(),
$float_field => array(
'value' => $random_float,
),
$integer_field => array(
'value' => $random_integer,
),
));
$node->save();
// Go to manage display page.
$this->drupalGet("admin/structure/types/manage/$type/display");
// Configure number_decimal formatter for the 'float' field type.
$thousand_separator = $thousand_separators[array_rand($thousand_separators)];
$decimal_separator = $decimal_separators[array_rand($decimal_separators)];
$scale = rand(0, 10);
$this->drupalPostAjaxForm(NULL, array(), "${float_field}_settings_edit");
$edit = array(
"fields[${float_field}][settings_edit_form][settings][prefix_suffix]" => TRUE,
"fields[${float_field}][settings_edit_form][settings][scale]" => $scale,
"fields[${float_field}][settings_edit_form][settings][decimal_separator]" => $decimal_separator,
"fields[${float_field}][settings_edit_form][settings][thousand_separator]" => $thousand_separator,
);
$this->drupalPostAjaxForm(NULL, $edit, "${float_field}_plugin_settings_update");
$this->drupalPostForm(NULL, array(), t('Save'));
// Check number_decimal and number_unformatted formatters behavior.
$this->drupalGet('node/' . $node->id());
$float_formatted = number_format($random_float, $scale, $decimal_separator, $thousand_separator);
$this->assertRaw("$prefix$float_formatted$suffix", 'Prefix and suffix added');
$this->assertRaw((string) $random_integer);
// Configure the number_decimal formatter.
entity_get_display('node', $type, 'default')
->setComponent($integer_field, array(
'type' => 'number_integer',
))
->save();
$this->drupalGet("admin/structure/types/manage/$type/display");
$thousand_separator = $thousand_separators[array_rand($thousand_separators)];
$this->drupalPostAjaxForm(NULL, array(), "${integer_field}_settings_edit");
$edit = array(
"fields[${integer_field}][settings_edit_form][settings][prefix_suffix]" => FALSE,
"fields[${integer_field}][settings_edit_form][settings][thousand_separator]" => $thousand_separator,
);
$this->drupalPostAjaxForm(NULL, $edit, "${integer_field}_plugin_settings_update");
$this->drupalPostForm(NULL, array(), t('Save'));
// Check number_integer formatter behavior.
$this->drupalGet('node/' . $node->id());
$integer_formatted = number_format($random_integer, 0, '', $thousand_separator);
$this->assertRaw($integer_formatted, 'Random integer formatted');
}
/**
* Tests setting the minimum value of a float field through the interface.
*/
function testCreateNumberFloatField() {
// Create a float field.
$field_name = Unicode::strtolower($this->randomMachineName());
entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'float',
))->save();
$field = entity_create('field_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
));
$field->save();
// Set the minimum value to a float value.
$this->assertSetMinimumValue($field, 0.0001);
// Set the minimum value to an integer value.
$this->assertSetMinimumValue($field, 1);
}
/**
* Tests setting the minimum value of a decimal field through the interface.
*/
function testCreateNumberDecimalField() {
// Create a decimal field.
$field_name = Unicode::strtolower($this->randomMachineName());
entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'decimal',
))->save();
$field = entity_create('field_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
));
$field->save();
// Set the minimum value to a decimal value.
$this->assertSetMinimumValue($field, 0.1);
// Set the minimum value to an integer value.
$this->assertSetMinimumValue($field, 1);
}
/**
* Helper function to set the minimum value of a field.
*/
function assertSetMinimumValue($field, $minimum_value) {
$field_configuration_url = 'entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field->getName();
// Set the minimum value.
$edit = array(
'settings[min]' => $minimum_value,
);
$this->drupalPostForm($field_configuration_url, $edit, t('Save settings'));
// Check if an error message is shown.
$this->assertNoRaw(t('%name is not a valid number.', array('%name' => t('Minimum'))), 'Saved ' . gettype($minimum_value) .' value as minimal value on a ' . $field->getType() . ' field');
// Check if a success message is shown.
$this->assertRaw(t('Saved %label configuration.', array('%label' => $field->getLabel())));
// Check if the minimum value was actually set.
$this->drupalGet($field_configuration_url);
$this->assertFieldById('edit-settings-min', $minimum_value, 'Minimal ' . gettype($minimum_value) .' value was set on a ' . $field->getType() . ' field.');
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Number\NumberItemTest.
*/
namespace Drupal\field\Tests\Number;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\field\Tests\FieldUnitTestBase;
/**
* Tests the new entity API for the number field type.
*
* @group field
*/
class NumberItemTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array();
protected function setUp() {
parent::setUp();
// Create number field storages and fields for validation.
foreach (array('integer', 'float', 'decimal') as $type) {
entity_create('field_storage_config', array(
'entity_type' => 'entity_test',
'field_name' => 'field_' . $type,
'type' => $type,
))->save();
entity_create('field_config', array(
'entity_type' => 'entity_test',
'field_name' => 'field_' . $type,
'bundle' => 'entity_test',
))->save();
}
}
/**
* Tests using entity fields of the number field type.
*/
public function testNumberItem() {
// Verify entity creation.
$entity = entity_create('entity_test');
$integer = rand(0, 10);
$entity->field_integer = $integer;
$float = 3.14;
$entity->field_float = $float;
$decimal = '31.3';
$entity->field_decimal = $decimal;
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertTrue($entity->field_integer instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_integer[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_integer->value, $integer);
$this->assertEqual($entity->field_integer[0]->value, $integer);
$this->assertTrue($entity->field_float instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_float[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_float->value, $float);
$this->assertEqual($entity->field_float[0]->value, $float);
$this->assertTrue($entity->field_decimal instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_decimal[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_decimal->value, $decimal);
$this->assertEqual($entity->field_decimal[0]->value, $decimal);
// Verify changing the number value.
$new_integer = rand(11, 20);
$new_float = rand(1001, 2000) / 100;
$new_decimal = '18.2';
$entity->field_integer->value = $new_integer;
$this->assertEqual($entity->field_integer->value, $new_integer);
$entity->field_float->value = $new_float;
$this->assertEqual($entity->field_float->value, $new_float);
$entity->field_decimal->value = $new_decimal;
$this->assertEqual($entity->field_decimal->value, $new_decimal);
// Read changed entity and assert changed values.
$entity->save();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->field_integer->value, $new_integer);
$this->assertEqual($entity->field_float->value, $new_float);
$this->assertEqual($entity->field_decimal->value, $new_decimal);
/// Test sample item generation.
$entity = entity_create('entity_test');
$entity->field_integer->generateSampleItems();
$entity->field_float->generateSampleItems();
$entity->field_decimal->generateSampleItems();
$this->entityValidateAndSave($entity);
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\ShapeItemTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Tests the new entity API for the shape field type.
*
* @group field
*/
class ShapeItemTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field_test');
/**
* The name of the field to use in this test.
*
* @var string
*/
protected $fieldName = 'field_shape';
protected function setUp() {
parent::setUp();
// Create a 'shape' field and storage for validation.
entity_create('field_storage_config', array(
'field_name' => $this->fieldName,
'entity_type' => 'entity_test',
'type' => 'shape',
))->save();
entity_create('field_config', array(
'entity_type' => 'entity_test',
'field_name' => $this->fieldName,
'bundle' => 'entity_test',
))->save();
}
/**
* Tests using entity fields of the field field type.
*/
public function testShapeItem() {
// Verify entity creation.
$entity = entity_create('entity_test');
$shape = 'cube';
$color = 'blue';
$entity->{$this->fieldName}->shape = $shape;
$entity->{$this->fieldName}->color = $color;
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertTrue($entity->{$this->fieldName} instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->{$this->fieldName}[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->{$this->fieldName}->shape, $shape);
$this->assertEqual($entity->{$this->fieldName}->color, $color);
$this->assertEqual($entity->{$this->fieldName}[0]->shape, $shape);
$this->assertEqual($entity->{$this->fieldName}[0]->color, $color);
// Verify changing the field value.
$new_shape = 'circle';
$new_color = 'red';
$entity->{$this->fieldName}->shape = $new_shape;
$entity->{$this->fieldName}->color = $new_color;
$this->assertEqual($entity->{$this->fieldName}->shape, $new_shape);
$this->assertEqual($entity->{$this->fieldName}->color, $new_color);
// Read changed entity and assert changed values.
$entity->save();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->{$this->fieldName}->shape, $new_shape);
$this->assertEqual($entity->{$this->fieldName}->color, $new_color);
}
}

View file

@ -0,0 +1,129 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\String\RawStringFormatterTest.
*/
namespace Drupal\field\Tests\String;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the raw string formatter
*
* @group field
*/
class RawStringFormatterTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field', 'text', 'entity_test', 'system', 'filter', 'user');
/**
* @var string
*/
protected $entityType;
/**
* @var string
*/
protected $bundle;
/**
* @var string
*/
protected $fieldName;
/**
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
*/
protected $display;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Configure the theme system.
$this->installConfig(array('system', 'field'));
$this->installSchema('system', 'router');
\Drupal::service('router.builder')->rebuild();
$this->installEntitySchema('entity_test');
$this->entityType = 'entity_test';
$this->bundle = $this->entityType;
$this->fieldName = Unicode::strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create(array(
'field_name' => $this->fieldName,
'entity_type' => $this->entityType,
'type' => 'string_long',
));
$field_storage->save();
$instance = FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => $this->bundle,
'label' => $this->randomMachineName(),
));
$instance->save();
$this->display = entity_get_display($this->entityType, $this->bundle, 'default')
->setComponent($this->fieldName, array(
'type' => 'string',
'settings' => array(),
));
$this->display->save();
}
/**
* Renders fields of a given entity with a given display.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity object with attached fields to render.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The display to render the fields in.
*
* @return string
* The rendered entity fields.
*/
protected function renderEntityFields(FieldableEntityInterface $entity, EntityViewDisplayInterface $display) {
$content = $display->build($entity);
$content = $this->render($content);
return $content;
}
/**
* Tests string formatter output.
*/
public function testStringFormatter() {
$value = $this->randomString();
$value .= "\n\n<strong>" . $this->randomString() . '</strong>';
$value .= "\n\n" . $this->randomString();
$entity = EntityTest::create(array());
$entity->{$this->fieldName}->value = $value;
// Verify that all HTML is escaped and newlines are retained.
$this->renderEntityFields($entity, $this->display);
$this->assertNoRaw($value);
$this->assertRaw(nl2br(SafeMarkup::checkPlain($value)));
// Verify the cache tags.
$build = $entity->{$this->fieldName}->view();
$this->assertTrue(!isset($build[0]['#cache']), format_string('The string formatter has no cache tags.'));
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\String\StringFieldTest.
*/
namespace Drupal\field\Tests\String;
use Drupal\Component\Utility\Unicode;
use Drupal\simpletest\WebTestBase;
/**
* Tests the creation of string fields.
*
* @group text
*/
class StringFieldTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_test');
/**
* A user without any special permissions.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
$this->webUser = $this->drupalCreateUser(array('view test entity', 'administer entity_test content'));
$this->drupalLogin($this->webUser);
}
// Test fields.
/**
* Test widgets.
*/
function testTextfieldWidgets() {
$this->_testTextfieldWidgets('string', 'string_textfield');
$this->_testTextfieldWidgets('string_long', 'string_textarea');
}
/**
* Helper function for testTextfieldWidgets().
*/
function _testTextfieldWidgets($field_type, $widget_type) {
// Create a field.
$field_name = Unicode::strtolower($this->randomMachineName());
$field_storage = entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => $field_type
));
$field_storage->save();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'entity_test',
'label' => $this->randomMachineName() . '_label',
))->save();
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => $widget_type,
'settings' => array(
'placeholder' => 'A placeholder on ' . $widget_type,
),
))
->save();
entity_get_display('entity_test', 'entity_test', 'full')
->setComponent($field_name)
->save();
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertNoFieldByName("{$field_name}[0][format]", '1', 'Format selector is not displayed');
$this->assertRaw(format_string('placeholder="A placeholder on !widget_type"', array('!widget_type' => $widget_type)));
// Submit with some value.
$value = $this->randomMachineName();
$edit = array(
"{$field_name}[0][value]" => $value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
// Display the entity.
$entity = entity_load('entity_test', $id);
$display = entity_get_display($entity->getEntityTypeId(), $entity->bundle(), 'full');
$content = $display->build($entity);
$this->setRawContent(\Drupal::service('renderer')->renderRoot($content));
$this->assertText($value, 'Filtered tags are not displayed');
}
}

View file

@ -0,0 +1,165 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\String\StringFormatterTest.
*/
namespace Drupal\field\Tests\String;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\entity_test\Entity\EntityTestRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the creation of text fields.
*
* @group field
*/
class StringFormatterTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field', 'text', 'entity_test', 'system', 'filter', 'user');
/**
* @var string
*/
protected $entityType;
/**
* @var string
*/
protected $bundle;
/**
* @var string
*/
protected $fieldName;
/**
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
*/
protected $display;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Configure the theme system.
$this->installConfig(array('system', 'field'));
$this->installSchema('system', 'router');
\Drupal::service('router.builder')->rebuild();
$this->installEntitySchema('entity_test_rev');
$this->entityType = 'entity_test_rev';
$this->bundle = $this->entityType;
$this->fieldName = Unicode::strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create(array(
'field_name' => $this->fieldName,
'entity_type' => $this->entityType,
'type' => 'string',
));
$field_storage->save();
$instance = FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => $this->bundle,
'label' => $this->randomMachineName(),
));
$instance->save();
$this->display = entity_get_display($this->entityType, $this->bundle, 'default')
->setComponent($this->fieldName, array(
'type' => 'string',
'settings' => array(),
));
$this->display->save();
}
/**
* Renders fields of a given entity with a given display.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity object with attached fields to render.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The display to render the fields in.
*
* @return string
* The rendered entity fields.
*/
protected function renderEntityFields(FieldableEntityInterface $entity, EntityViewDisplayInterface $display) {
$content = $display->build($entity);
$content = $this->render($content);
return $content;
}
/**
* Tests string formatter output.
*/
public function testStringFormatter() {
$value = $this->randomString();
$value .= "\n\n<strong>" . $this->randomString() . '</strong>';
$value .= "\n\n" . $this->randomString();
$entity = EntityTestRev::create(array());
$entity->{$this->fieldName}->value = $value;
// Verify that all HTML is escaped and newlines are retained.
$this->renderEntityFields($entity, $this->display);
$this->assertNoRaw($value);
$this->assertRaw(nl2br(SafeMarkup::checkPlain($value)));
// Verify the cache tags.
$build = $entity->{$this->fieldName}->view();
$this->assertTrue(!isset($build[0]['#cache']), format_string('The string formatter has no cache tags.'));
$value = $this->randomMachineName();
$entity->{$this->fieldName}->value = $value;
$entity->save();
// Set the formatter to link to the entity.
$this->display->setComponent($this->fieldName, [
'type' => 'string',
'settings' => [
'link_to_entity' => TRUE,
],
]);
$this->display->save();
$this->renderEntityFields($entity, $this->display);
$this->assertLink($value, 0);
$this->assertLinkByHref($entity->url());
// $entity->url('revision') falls back to the canonical URL if this is no
// revision.
$this->assertLinkByHref($entity->url('revision'));
// Make the entity a new revision.
$old_revision_id = $entity->getRevisionId();
$entity->setNewRevision(TRUE);
$value2 = $this->randomMachineName();
$entity->{$this->fieldName}->value = $value2;
$entity->save();
$entity_new_revision = \Drupal::entityManager()->getStorage('entity_test_rev')->loadRevision($old_revision_id);
$this->renderEntityFields($entity, $this->display);
$this->assertLink($value2, 0);
$this->assertLinkByHref($entity->url('revision'));
$this->renderEntityFields($entity_new_revision, $this->display);
$this->assertLink($value, 0);
$this->assertLinkByHref('/entity_test_rev/' . $entity_new_revision->id() . '/revision/' . $entity_new_revision->getRevisionId() . '/view');
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\String\UuidFormatterTest.
*/
namespace Drupal\field\Tests\String;
use Drupal\simpletest\KernelTestBase;
use Drupal\entity_test\Entity\EntityTest;
/**
* Tests the output of a UUID field.
*
* @group field
*/
class UuidFormatterTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['field', 'entity_test', 'system', 'user'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(['system', 'field']);
$this->installSchema('system', 'router');
\Drupal::service('router.builder')->rebuild();
$this->installEntitySchema('entity_test');
}
/**
* Tests string formatter output.
*/
public function testUuidStringFormatter() {
$entity = EntityTest::create([]);
$entity->save();
$uuid_field = $entity->get('uuid');
$render_array = $uuid_field->view([]);
$this->assertIdentical($render_array[0]['#markup'], $entity->uuid(), 'The rendered UUID matches the entity UUID.');
$render_array = $uuid_field->view(['settings' => ['link_to_entity' => TRUE]]);
$this->assertIdentical($render_array[0]['#type'], 'link');
$this->assertIdentical($render_array[0]['#title'], $entity->uuid());
$this->assertIdentical($render_array[0]['#url']->toString(), $entity->url());
}
}

View file

@ -0,0 +1,98 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\TestItemTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Tests the new entity API for the test field type.
*
* @group field
*/
class TestItemTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field_test');
/**
* The name of the field to use in this test.
*
* @var string
*/
protected $fieldName = 'field_test';
protected function setUp() {
parent::setUp();
// Create a 'test_field' field and storage for validation.
entity_create('field_storage_config', array(
'field_name' => $this->fieldName,
'entity_type' => 'entity_test',
'type' => 'test_field',
))->save();
entity_create('field_config', array(
'entity_type' => 'entity_test',
'field_name' => $this->fieldName,
'bundle' => 'entity_test',
))->save();
}
/**
* Tests using entity fields of the field field type.
*/
public function testTestItem() {
// Verify entity creation.
$entity = entity_create('entity_test');
$value = rand(1, 10);
$entity->field_test = $value;
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertTrue($entity->{$this->fieldName} instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->{$this->fieldName}[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->{$this->fieldName}->value, $value);
$this->assertEqual($entity->{$this->fieldName}[0]->value, $value);
// Verify changing the field value.
$new_value = rand(1, 10);
$entity->field_test->value = $new_value;
$this->assertEqual($entity->{$this->fieldName}->value, $new_value);
// Read changed entity and assert changed values.
$entity->save();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->{$this->fieldName}->value, $new_value);
// Test the schema for this field type.
$expected_schema = array(
'columns' => array(
'value' => array(
'type' => 'int',
'size' => 'medium',
),
),
'unique keys' => array(),
'indexes' => array(
'value' => array('value'),
),
'foreign keys' => array(),
);
$field_schema = BaseFieldDefinition::create('test_field')->getSchema();
$this->assertEqual($field_schema, $expected_schema);
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\TestItemWithDependenciesTest.
*/
namespace Drupal\field\Tests;
/**
* Tests the new entity API for the test field with dependencies type.
*
* @group field
*/
class TestItemWithDependenciesTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field_test');
/**
* The name of the field to use in this test.
*
* @var string
*/
protected $fieldName = 'field_test';
/**
* Tests that field types can add dependencies to field config entities.
*/
public function testTestItemWithDepenencies() {
// Create a 'test_field_with_dependencies' field and storage for validation.
entity_create('field_storage_config', array(
'field_name' => $this->fieldName,
'entity_type' => 'entity_test',
'type' => 'test_field_with_dependencies',
))->save();
$field = entity_create('field_config', array(
'entity_type' => 'entity_test',
'field_name' => $this->fieldName,
'bundle' => 'entity_test',
));
$field->save();
// Validate that the field configuration entity has the expected
// dependencies.
$this->assertEqual([
'content' => ['node:article:uuid'],
'config' => ['field.storage.entity_test.field_test'],
'module' => ['field_test', 'test_module']
], $field->getDependencies());
}
}

View file

@ -0,0 +1,194 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Timestamp\TimestampFormatterTest.
*/
namespace Drupal\field\Tests\Timestamp;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the timestamp formatters.
*
* @group field
*/
class TimestampFormatterTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'field', 'text', 'entity_test', 'user'];
/**
* @var string
*/
protected $entityType;
/**
* @var string
*/
protected $bundle;
/**
* @var string
*/
protected $fieldName;
/**
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
*/
protected $display;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(['system']);
$this->installConfig(['field']);
$this->installEntitySchema('entity_test');
$this->entityType = 'entity_test';
$this->bundle = $this->entityType;
$this->fieldName = Unicode::strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => $this->entityType,
'type' => 'timestamp',
]);
$field_storage->save();
$instance = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $this->bundle,
'label' => $this->randomMachineName(),
]);
$instance->save();
$this->display = entity_get_display($this->entityType, $this->bundle, 'default')
->setComponent($this->fieldName, [
'type' => 'boolean',
'settings' => [],
]);
$this->display->save();
}
/**
* Renders fields of a given entity with a given display.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity object with attached fields to render.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The display to render the fields in.
*
* @return string
* The rendered entity fields.
*/
protected function renderEntityFields(FieldableEntityInterface $entity, EntityViewDisplayInterface $display) {
$content = $display->build($entity);
$content = $this->render($content);
return $content;
}
/**
* Tests TimestampFormatter.
*/
protected function testTimestampFormatter() {
$data = [];
// Test standard formats.
$date_formats = array_keys(\Drupal::entityManager()->getStorage('date_format')->loadMultiple());
foreach ($date_formats as $date_format) {
$data[] = ['date_format' => $date_format, 'custom_date_format' => '', 'timezone' => ''];
}
$data[] = ['date_format' => 'custom', 'custom_date_format' => 'r', 'timezone' => ''];
$data[] = ['date_format' => 'custom', 'custom_date_format' => 'e', 'timezone' => 'Asia/Tokyo'];
foreach ($data as $settings) {
list($date_format, $custom_date_format, $timezone) = array_values($settings);
if (empty($timezone)) {
$timezone = NULL;
}
$value = REQUEST_TIME - 87654321;
$expected = \Drupal::service('date.formatter')->format($value, $date_format, $custom_date_format, $timezone);
$component = $this->display->getComponent($this->fieldName);
$component['type'] = 'timestamp';
$component['settings'] = $settings;
$this->display->setComponent($this->fieldName, $component);
$entity = EntityTest::create([]);
$entity->{$this->fieldName}->value = $value;
$this->renderEntityFields($entity, $this->display);
$this->assertRaw($expected);
}
}
/**
* Tests TimestampAgoFormatter.
*/
protected function testTimestampAgoFormatter() {
$data = [];
foreach (array(1,2,3,4,5,6) as $granularity) {
$data[] = [
'future_format' => '@interval hence',
'past_format' => '@interval ago',
'granularity' => $granularity,
];
}
foreach ($data as $settings) {
$future_format = $settings['future_format'];
$past_format = $settings['past_format'];
$granularity = $settings['granularity'];
$request_time = \Drupal::requestStack()->getCurrentRequest()->server->get('REQUEST_TIME');
// Test a timestamp in the past
$value = $request_time - 87654321;
$expected = SafeMarkup::format($past_format, ['@interval' => \Drupal::service('date.formatter')->formatTimeDiffSince($value, ['granularity' => $granularity])]);
$component = $this->display->getComponent($this->fieldName);
$component['type'] = 'timestamp_ago';
$component['settings'] = $settings;
$this->display->setComponent($this->fieldName, $component);
$entity = EntityTest::create([]);
$entity->{$this->fieldName}->value = $value;
$this->renderEntityFields($entity, $this->display);
$this->assertRaw($expected);
// Test a timestamp in the future
$value = $request_time + 87654321;
$expected = SafeMarkup::format($future_format, ['@interval' => \Drupal::service('date.formatter')->formatTimeDiffUntil($value, ['granularity' => $granularity])]);
$component = $this->display->getComponent($this->fieldName);
$component['type'] = 'timestamp_ago';
$component['settings'] = $settings;
$this->display->setComponent($this->fieldName, $component);
$entity = EntityTest::create([]);
$entity->{$this->fieldName}->value = $value;
$this->renderEntityFields($entity, $this->display);
$this->assertRaw($expected);
}
}
}

View file

@ -0,0 +1,205 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\TranslationTest.
*/
namespace Drupal\field\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests multilanguage fields logic.
*
* The following tests will check the multilanguage logic in field handling.
*
* @group field
*/
class TranslationTest extends FieldUnitTestBase {
/**
* Modules to enable.
*
* node is required because the tests alter the node entity type.
*
* @var array
*/
public static $modules = array('language', 'node');
/**
* The name of the field to use in this test.
*
* @var string
*/
protected $fieldName;
/**
* The name of the entity type to use in this test.
*
* @var string
*/
protected $entityType = 'test_entity';
/**
* An array defining the field storage to use in this test.
*
* @var array
*/
protected $fieldStorageDefinition;
/**
* An array defining the field to use in this test.
*
* @var array
*/
protected $fieldDefinition;
/**
* The field storage to use in this test.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field to use in this test.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('language'));
$this->fieldName = Unicode::strtolower($this->randomMachineName());
$this->entityType = 'entity_test';
$this->fieldStorageDefinition = array(
'field_name' => $this->fieldName,
'entity_type' => $this->entityType,
'type' => 'test_field',
'cardinality' => 4,
);
$this->fieldStorage = entity_create('field_storage_config', $this->fieldStorageDefinition);
$this->fieldStorage->save();
$this->fieldDefinition = array(
'field_storage' => $this->fieldStorage,
'bundle' => 'entity_test',
);
$this->field = entity_create('field_config', $this->fieldDefinition);
$this->field->save();
for ($i = 0; $i < 3; ++$i) {
ConfigurableLanguage::create(array(
'id' => 'l' . $i,
'label' => $this->randomString(),
))->save();
}
}
/**
* Test translatable fields storage/retrieval.
*/
function testTranslatableFieldSaveLoad() {
// Enable field translations for nodes.
field_test_entity_info_translatable('node', TRUE);
$entity_type = \Drupal::entityManager()->getDefinition('node');
$this->assertTrue($entity_type->isTranslatable(), 'Nodes are translatable.');
// Prepare the field translations.
$entity_type_id = 'entity_test';
field_test_entity_info_translatable($entity_type_id, TRUE);
$entity = entity_create($entity_type_id, array('type' => $this->field->getTargetBundle()));
$field_translations = array();
$available_langcodes = array_keys($this->container->get('language_manager')->getLanguages());
$entity->langcode->value = reset($available_langcodes);
foreach ($available_langcodes as $langcode) {
$field_translations[$langcode] = $this->_generateTestFieldValues($this->fieldStorage->getCardinality());
$entity->getTranslation($langcode)->{$this->fieldName}->setValue($field_translations[$langcode]);
}
// Save and reload the field translations.
$entity = $this->entitySaveReload($entity);
// Check if the correct values were saved/loaded.
foreach ($field_translations as $langcode => $items) {
$result = TRUE;
foreach ($items as $delta => $item) {
$result = $result && $item['value'] == $entity->getTranslation($langcode)->{$this->fieldName}[$delta]->value;
}
$this->assertTrue($result, format_string('%language translation correctly handled.', array('%language' => $langcode)));
}
// Test default values.
$field_name_default = Unicode::strtolower($this->randomMachineName() . '_field_name');
$field_storage_definition = $this->fieldStorageDefinition;
$field_storage_definition['field_name'] = $field_name_default;
$field_storage = entity_create('field_storage_config', $field_storage_definition);
$field_storage->save();
$field_definition = $this->fieldDefinition;
$field_definition['field_storage'] = $field_storage;
$field_definition['default_value'] = array(array('value' => rand(1, 127)));
$field = entity_create('field_config', $field_definition);
$field->save();
$translation_langcodes = array_slice($available_langcodes, 0, 2);
asort($translation_langcodes);
$translation_langcodes = array_values($translation_langcodes);
$values = array('type' => $field->getTargetBundle(), 'langcode' => $translation_langcodes[0]);
$entity = entity_create($entity_type_id, $values);
foreach ($translation_langcodes as $langcode) {
$values[$this->fieldName][$langcode] = $this->_generateTestFieldValues($this->fieldStorage->getCardinality());
$entity->getTranslation($langcode, FALSE)->{$this->fieldName}->setValue($values[$this->fieldName][$langcode]);
}
$field_langcodes = array_keys($entity->getTranslationLanguages());
sort($field_langcodes);
$this->assertEqual($translation_langcodes, $field_langcodes, 'Missing translations did not get a default value.');
// @todo Test every translation once the Entity Translation API allows for
// multilingual defaults.
$langcode = $entity->language()->getId();
$this->assertEqual($entity->getTranslation($langcode)->{$field_name_default}->getValue(), $field->default_value, format_string('Default value correctly populated for language %language.', array('%language' => $langcode)));
// Check that explicit empty values are not overridden with default values.
foreach (array(NULL, array()) as $empty_items) {
$values = array('type' => $field->getTargetBundle(), 'langcode' => $translation_langcodes[0]);
$entity = entity_create($entity_type_id, $values);
foreach ($translation_langcodes as $langcode) {
$values[$this->fieldName][$langcode] = $this->_generateTestFieldValues($this->fieldStorage->getCardinality());
$entity->getTranslation($langcode)->{$this->fieldName}->setValue($values[$this->fieldName][$langcode]);
$entity->getTranslation($langcode)->{$field_name_default}->setValue($empty_items);
$values[$field_name_default][$langcode] = $empty_items;
}
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
$this->assertEqual($entity->getTranslation($langcode)->{$field_name_default}->getValue(), $empty_items, format_string('Empty value correctly populated for language %language.', array('%language' => $langcode)));
}
}
}
/**
* Tests field access.
*
* Regression test to verify that fieldAccess() can be called while only
* passing the required parameters.
*
* @see https://www.drupal.org/node/2404739
*/
public function testFieldAccess() {
$access_control_handler = \Drupal::entityManager()->getAccessControlHandler($this->entityType);
$this->assertTrue($access_control_handler->fieldAccess('view', $this->field));
}
}

View file

@ -0,0 +1,135 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\TranslationWebTest.
*/
namespace Drupal\field\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests multilanguage fields logic that require a full environment.
*
* @group field
*/
class TranslationWebTest extends FieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'field_test', 'entity_test');
/**
* The name of the field to use in this test.
*
* @var string
*/
protected $fieldName;
/**
* The name of the entity type to use in this test.
*
* @var string
*/
protected $entityTypeId = 'entity_test_mulrev';
/**
* The field storage to use in this test.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field to use in this test.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
protected function setUp() {
parent::setUp();
$this->fieldName = Unicode::strtolower($this->randomMachineName() . '_field_name');
$field_storage = array(
'field_name' => $this->fieldName,
'entity_type' => $this->entityTypeId,
'type' => 'test_field',
'cardinality' => 4,
);
entity_create('field_storage_config', $field_storage)->save();
$this->fieldStorage = FieldStorageConfig::load($this->entityTypeId . '.' . $this->fieldName);
$field = array(
'field_storage' => $this->fieldStorage,
'bundle' => $this->entityTypeId,
);
entity_create('field_config', $field)->save();
$this->field = FieldConfig::load($this->entityTypeId . '.' . $field['bundle'] . '.' . $this->fieldName);
entity_get_form_display($this->entityTypeId, $this->entityTypeId, 'default')
->setComponent($this->fieldName)
->save();
for ($i = 0; $i < 3; ++$i) {
ConfigurableLanguage::create(array(
'id' => 'l' . $i,
'label' => $this->randomString(),
))->save();
}
}
/**
* Tests field translations when creating a new revision.
*/
function testFieldFormTranslationRevisions() {
$web_user = $this->drupalCreateUser(array('view test entity', 'administer entity_test content'));
$this->drupalLogin($web_user);
// Prepare the field translations.
field_test_entity_info_translatable($this->entityTypeId, TRUE);
$entity = entity_create($this->entityTypeId);
$available_langcodes = array_flip(array_keys($this->container->get('language_manager')->getLanguages()));
$field_name = $this->fieldStorage->getName();
// Store the field translations.
ksort($available_langcodes);
$entity->langcode->value = key($available_langcodes);
foreach ($available_langcodes as $langcode => $value) {
$entity->getTranslation($langcode)->{$field_name}->value = $value + 1;
}
$entity->save();
// Create a new revision.
$edit = array(
"{$field_name}[0][value]" => $entity->{$field_name}->value,
'revision' => TRUE,
);
$this->drupalPostForm($this->entityTypeId . '/manage/' . $entity->id(), $edit, t('Save'));
// Check translation revisions.
$this->checkTranslationRevisions($entity->id(), $entity->getRevisionId(), $available_langcodes);
$this->checkTranslationRevisions($entity->id(), $entity->getRevisionId() + 1, $available_langcodes);
}
/**
* Check if the field translation attached to the entity revision identified
* by the passed arguments were correctly stored.
*/
private function checkTranslationRevisions($id, $revision_id, $available_langcodes) {
$field_name = $this->fieldStorage->getName();
$entity = entity_revision_load($this->entityTypeId, $revision_id);
foreach ($available_langcodes as $langcode => $value) {
$passed = $entity->getTranslation($langcode)->{$field_name}->value == $value + 1;
$this->assertTrue($passed, format_string('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->getRevisionId())));
}
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Views\FieldTestBase.
*/
/**
* @TODO
* - Test on a generic entity not on a node.
*
* What has to be tested:
* - Make sure that every wanted field is added to the according entity type.
* - Make sure the joins are done correctly.
* - Use basic fields and make sure that the full wanted object is built.
* - Use relationships between different entity types, for example node and
* the node author(user).
*/
namespace Drupal\field\Tests\Views;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Provides some helper methods for testing fieldapi integration into views.
*/
abstract class FieldTestBase extends ViewTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'field_test_views');
/**
* Stores the field definitions used by the test.
*
* @var array
*/
public $fieldStorages;
/**
* Stores the fields of the field storage. They have the same keys as the
* field storages.
*
* @var array
*/
public $fields;
protected function setUp() {
parent::setUp();
// Ensure the page node type exists.
entity_create('node_type', array(
'type' => 'page',
'name' => 'page',
))->save();
ViewTestData::createTestViews(get_class($this), array('field_test_views'));
}
function setUpFieldStorages($amount = 3, $type = 'string') {
// Create three fields.
$field_names = array();
for ($i = 0; $i < $amount; $i++) {
$field_names[$i] = 'field_name_' . $i;
$this->fieldStorages[$i] = entity_create('field_storage_config', array(
'field_name' => $field_names[$i],
'entity_type' => 'node',
'type' => $type,
));
$this->fieldStorages[$i]->save();
}
return $field_names;
}
function setUpFields($bundle = 'page') {
foreach ($this->fieldStorages as $key => $field_storage) {
$this->fields[$key] = entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => $bundle,
));
$this->fields[$key]->save();
}
}
}

View file

@ -0,0 +1,115 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Views\FieldUITest.
*/
namespace Drupal\field\Tests\Views;
use Drupal\views\Views;
/**
* Tests the UI of the field field handler.
*
* @group field
* @see \Drupal\field\Plugin\views\field\Field
*/
class FieldUITest extends FieldTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_view_fieldapi');
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('views_ui');
/**
* A user with the 'administer views' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $account;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->account = $this->drupalCreateUser(array('administer views'));
$this->drupalLogin($this->account);
$this->setUpFieldStorages(1, 'text');
$this->setUpFields();
}
/**
* Tests basic field handler settings in the UI.
*/
public function testHandlerUI() {
$url = "admin/structure/views/nojs/handler/test_view_fieldapi/default/field/field_name_0";
$this->drupalGet($url);
// Tests the available formatter options.
$result = $this->xpath('//select[@id=:id]/option', array(':id' => 'edit-options-type'));
$options = array_map(function($item) {
return (string) $item->attributes()->value[0];
}, $result);
// @todo Replace this sort by assertArray once it's in.
sort($options, SORT_STRING);
$this->assertEqual($options, array('text_default', 'text_trimmed'), 'The text formatters for a simple text field appear as expected.');
$this->drupalPostForm(NULL, array('options[type]' => 'text_trimmed'), t('Apply'));
$this->drupalGet($url);
$this->assertOptionSelected('edit-options-type', 'text_trimmed');
$random_number = rand(100, 400);
$this->drupalPostForm(NULL, array('options[settings][trim_length]' => $random_number), t('Apply'));
$this->drupalGet($url);
$this->assertFieldByName('options[settings][trim_length]', $random_number, 'The formatter setting got saved.');
// Save the view and test whether the settings are saved.
$this->drupalPostForm('admin/structure/views/view/test_view_fieldapi', array(), t('Save'));
$view = Views::getView('test_view_fieldapi');
$view->initHandlers();
$this->assertEqual($view->field['field_name_0']->options['type'], 'text_trimmed');
$this->assertEqual($view->field['field_name_0']->options['settings']['trim_length'], $random_number);
// Ensure that the view depends on the field storage.
$dependencies = \Drupal::service('config.manager')->findConfigEntityDependents('config', [$this->fieldStorages[0]->getConfigDependencyName()]);
$this->assertTrue(isset($dependencies['views.view.test_view_fieldapi']), 'The view is dependent on the field storage.');
}
/**
* Tests the basic field handler form when aggregation is enabled.
*/
public function testHandlerUIAggregation() {
// Enable aggregation.
$edit = array('group_by' => '1');
$this->drupalPostForm('admin/structure/views/nojs/display/test_view_fieldapi/default/group_by', $edit, t('Apply'));
$url = "admin/structure/views/nojs/handler/test_view_fieldapi/default/field/field_name_0";
$this->drupalGet($url);
$this->assertResponse(200);
// Test the click sort column options.
// Tests the available formatter options.
$result = $this->xpath('//select[@id=:id]/option', array(':id' => 'edit-options-click-sort-column'));
$options = array_map(function($item) {
return (string) $item->attributes()->value[0];
}, $result);
sort($options, SORT_STRING);
$this->assertEqual($options, array('format', 'value'), 'The expected sort field options were found.');
}
}

View file

@ -0,0 +1,314 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\Views\HandlerFieldFieldTest.
*/
namespace Drupal\field\Tests\Views;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
/**
* Tests the field itself of the Field integration.
*
* @group field
* @TODO
* Check a entity-type with bundles
* Check a entity-type without bundles
* Check locale:disabled, locale:enabled and locale:enabled with another language
* Check revisions
*/
class HandlerFieldFieldTest extends FieldTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('node', 'field_test');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_view_fieldapi');
/**
* Test nodes.
*
* @var \Drupal\node\NodeInterface[]
*/
public $nodes;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Setup basic fields.
$this->setUpFieldStorages(3);
// Setup a field with cardinality > 1.
$this->fieldStorages[3] = entity_create('field_storage_config', array(
'field_name' => 'field_name_3',
'entity_type' => 'node',
'type' => 'string',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
));
$this->fieldStorages[3]->save();
// Setup a field that will have no value.
$this->fieldStorages[4] = entity_create('field_storage_config', array(
'field_name' => 'field_name_4',
'entity_type' => 'node',
'type' => 'string',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
));
$this->fieldStorages[4]->save();
// Setup a text field.
$this->fieldStorages[5] = entity_create('field_storage_config', array(
'field_name' => 'field_name_5',
'entity_type' => 'node',
'type' => 'text',
));
$this->fieldStorages[5]->save();
// Setup a text field with access control.
// @see field_test_entity_field_access()
$this->fieldStorages[6] = entity_create('field_storage_config', array(
'field_name' => 'field_no_view_access',
'entity_type' => 'node',
'type' => 'text',
));
$this->fieldStorages[6]->save();
$this->setUpFields();
// Create some nodes.
$this->nodes = array();
for ($i = 0; $i < 3; $i++) {
$edit = array('type' => 'page');
foreach (array(0, 1, 2, 5) as $key) {
$field_storage = $this->fieldStorages[$key];
$edit[$field_storage->getName()][0]['value'] = $this->randomMachineName(8);
}
// Add a hidden value for the no-view field.
$edit[$this->fieldStorages[6]->getName()][0]['value'] = 'ssh secret squirrel';
for ($j = 0; $j < 5; $j++) {
$edit[$this->fieldStorages[3]->getName()][$j]['value'] = $this->randomMachineName(8);
}
// Set this field to be empty.
$edit[$this->fieldStorages[4]->getName()] = array(array('value' => NULL));
$this->nodes[$i] = $this->drupalCreateNode($edit);
}
$this->container->get('views.views_data')->clear();
}
/**
* Sets up the testing view with random field data.
*
* @param \Drupal\views\ViewExecutable $view
* The view to add field data to.
*/
protected function prepareView(ViewExecutable $view) {
$view->storage->invalidateCaches();
$view->initDisplay();
foreach ($this->fieldStorages as $field_storage) {
$field_name = $field_storage->getName();
$view->display_handler->options['fields'][$field_name]['id'] = $field_name;
$view->display_handler->options['fields'][$field_name]['table'] = 'node__' . $field_name;
$view->display_handler->options['fields'][$field_name]['field'] = $field_name;
}
}
public function testFieldRender() {
$this->_testSimpleFieldRender();
$this->_testInaccessibleFieldRender();
$this->_testFormatterSimpleFieldRender();
$this->_testMultipleFieldRender();
}
public function _testSimpleFieldRender() {
$view = Views::getView('test_view_fieldapi');
$this->prepareView($view);
$this->executeView($view);
// Tests that the rendered fields match the actual value of the fields.
for ($i = 0; $i < 3; $i++) {
for ($key = 0; $key < 2; $key++) {
$field_name = $this->fieldStorages[$key]->getName();
$rendered_field = $view->style_plugin->getField($i, $field_name);
$expected_field = $this->nodes[$i]->$field_name->value;
$this->assertEqual($rendered_field, $expected_field);
}
}
}
public function _testInaccessibleFieldRender() {
$view = Views::getView('test_view_fieldapi');
$this->prepareView($view);
$this->executeView($view);
// Check that the field handler for the hidden field is correctly removed
// from the display.
// @see https://www.drupal.org/node/2382931
$this->assertFalse(array_key_exists('field_no_view_access', $view->field));
// Check that the access-denied field is not visible.
for ($i = 0; $i < 3; $i++) {
$field_name = $this->fieldStorages[6]->getName();
$rendered_field = $view->style_plugin->getField($i, $field_name);
$this->assertFalse($rendered_field, 'Hidden field not rendered');
}
}
/**
* Tests that fields with formatters runs as expected.
*/
public function _testFormatterSimpleFieldRender() {
$view = Views::getView('test_view_fieldapi');
$this->prepareView($view);
$view->displayHandlers->get('default')->options['fields'][$this->fieldStorages[5]->getName()]['type'] = 'text_trimmed';
$view->displayHandlers->get('default')->options['fields'][$this->fieldStorages[5]->getName()]['settings'] = array(
'trim_length' => 3,
);
$this->executeView($view);
// Make sure that the formatter works as expected.
// @TODO: actually there should be a specific formatter.
for ($i = 0; $i < 2; $i++) {
$rendered_field = $view->style_plugin->getField($i, $this->fieldStorages[5]->getName());
$this->assertEqual(strlen(html_entity_decode($rendered_field)), 3);
}
}
public function _testMultipleFieldRender() {
$view = Views::getView('test_view_fieldapi');
$field_name = $this->fieldStorages[3]->getName();
// Test delta limit.
$this->prepareView($view);
$view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3;
$this->executeView($view);
for ($i = 0; $i < 3; $i++) {
$rendered_field = $view->style_plugin->getField($i, $field_name);
$items = array();
$pure_items = $this->nodes[$i]->{$field_name}->getValue();
$pure_items = array_splice($pure_items, 0, 3);
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
$this->assertEqual($rendered_field, implode(', ', $items), 'The amount of items is limited.');
}
// Test that an empty field is rendered without error.
$view->style_plugin->getField(4, $this->fieldStorages[4]->getName());
$view->destroy();
// Test delta limit + offset
$this->prepareView($view);
$view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_offset'] = 1;
$this->executeView($view);
for ($i = 0; $i < 3; $i++) {
$rendered_field = $view->style_plugin->getField($i, $field_name);
$items = array();
$pure_items = $this->nodes[$i]->{$field_name}->getValue();
$pure_items = array_splice($pure_items, 1, 3);
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
$this->assertEqual($rendered_field, implode(', ', $items), 'The amount of items is limited and the offset is correct.');
}
$view->destroy();
// Test delta limit + reverse.
$this->prepareView($view);
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_offset'] = 0;
$view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_reversed'] = TRUE;
$this->executeView($view);
for ($i = 0; $i < 3; $i++) {
$rendered_field = $view->style_plugin->getField($i, $field_name);
$items = array();
$pure_items = $this->nodes[$i]->{$field_name}->getValue();
array_splice($pure_items, 0, -3);
$pure_items = array_reverse($pure_items);
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
$this->assertEqual($rendered_field, implode(', ', $items), 'The amount of items is limited and they are reversed.');
}
$view->destroy();
// Test delta first last.
$this->prepareView($view);
$view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 0;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_first_last'] = TRUE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_reversed'] = FALSE;
$this->executeView($view);
for ($i = 0; $i < 3; $i++) {
$rendered_field = $view->style_plugin->getField($i, $field_name);
$items = array();
$pure_items = $this->nodes[$i]->{$field_name}->getValue();
$items[] = $pure_items[0]['value'];
$items[] = $pure_items[4]['value'];
$this->assertEqual($rendered_field, implode(', ', $items), 'Items are limited to first and last.');
}
$view->destroy();
// Test delta limit + custom separator.
$this->prepareView($view);
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_first_last'] = FALSE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3;
$view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['separator'] = ':';
$this->executeView($view);
for ($i = 0; $i < 3; $i++) {
$rendered_field = $view->style_plugin->getField($i, $field_name);
$items = array();
$pure_items = $this->nodes[$i]->{$field_name}->getValue();
$pure_items = array_splice($pure_items, 0, 3);
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
$this->assertEqual($rendered_field, implode(':', $items), 'The amount of items is limited and the custom separator is correct.');
}
$view->destroy();
// Test separator with HTML, ensure it is escaped.
$this->prepareView($view);
$view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE;
$view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3;
$view->displayHandlers->get('default')->options['fields'][$field_name]['separator'] = '<h2>test</h2>';
$this->executeView($view);
for ($i = 0; $i < 3; $i++) {
$rendered_field = $view->style_plugin->getField($i, $field_name);
$items = [];
$pure_items = $this->nodes[$i]->{$field_name}->getValue();
$pure_items = array_splice($pure_items, 0, 3);
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
$this->assertEqual($rendered_field, implode('<h2>test</h2>', $items), 'The custom separator is correctly escaped.');
}
$view->destroy();
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\WidgetPluginManagerTest.
*/
namespace Drupal\field\Tests;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\WidgetPluginManager;
/**
* Tests the field widget manager.
*
* @group field
*/
class WidgetPluginManagerTest extends FieldUnitTestBase {
/**
* Tests that the widget definitions alter hook works.
*/
function testWidgetDefinitionAlter() {
$widget_definition = \Drupal::service('plugin.manager.field.widget')->getDefinition('test_field_widget_multiple');
// Test if hook_field_widget_info_alter is being called.
$this->assertTrue(in_array('test_field', $widget_definition['field_types']), "The 'test_field_widget_multiple' widget is enabled for the 'test_field' field type in field_test_field_widget_info_alter().");
}
/**
* Tests that getInstance falls back on default if current is not applicable.
*
* @see \Drupal\field\Tests\FormatterPluginManagerTest::testNotApplicableFallback()
*/
public function testNotApplicableFallback() {
/** @var WidgetPluginManager $widget_plugin_manager */
$widget_plugin_manager = \Drupal::service('plugin.manager.field.widget');
$base_field_definition = BaseFieldDefinition::create('test_field')
// Set a name that will make isApplicable() return TRUE.
->setName('field_multiwidgetfield');
$widget_options = array(
'field_definition' => $base_field_definition,
'form_mode' => 'default',
'configuration' => array(
'type' => 'test_field_widget_multiple',
),
);
$instance = $widget_plugin_manager->getInstance($widget_options);
$this->assertEqual($instance->getPluginId(), 'test_field_widget_multiple');
// Now do the same but with machine name field_onewidgetfield, because that
// makes isApplicable() return FALSE.
$base_field_definition->setName('field_onewidgetfield');
$instance = $widget_plugin_manager->getInstance($widget_options);
// Instance should be default widget.
$this->assertNotEqual($instance->getPluginId(), 'test_field_widget_multiple');
$this->assertEqual($instance->getPluginId(), 'test_field_widget');
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* @file
* Contains \Drupal\field\Tests\reEnableModuleFieldTest.
*/
namespace Drupal\field\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the behavior of a field module after being disabled and re-enabled.
*
* @group field
*/
class reEnableModuleFieldTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'field',
'node',
// We use telephone module instead of test_field because test_field is
// hidden and does not display on the admin/modules page.
'telephone'
);
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(array('type' => 'article'));
$this->drupalLogin($this->drupalCreateUser(array(
'create article content',
'edit own article content',
)));
}
/**
* Test the behavior of a field module after being disabled and re-enabled.
*
* @see field_system_info_alter()
*/
function testReEnabledField() {
// Add a telephone field to the article content type.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'field_telephone',
'entity_type' => 'node',
'type' => 'telephone',
));
$field_storage->save();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'article',
'label' => 'Telephone Number',
))->save();
entity_get_form_display('node', 'article', 'default')
->setComponent('field_telephone', array(
'type' => 'telephone_default',
'settings' => array(
'placeholder' => '123-456-7890',
),
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent('field_telephone', array(
'type' => 'telephone_link',
'weight' => 1,
))
->save();
// Display the article node form and verify the telephone widget is present.
$this->drupalGet('node/add/article');
$this->assertFieldByName("field_telephone[0][value]", '', 'Widget found.');
// Submit an article node with a telephone field so data exist for the
// field.
$edit = array(
'title[0][value]' => $this->randomMachineName(),
'field_telephone[0][value]' => "123456789",
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw('<a href="tel:123456789">');
// Test that the module can't be uninstalled from the UI while there is data
// for it's fields.
$admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
$this->drupalLogin($admin_user);
$this->drupalGet('admin/modules/uninstall');
$this->assertText('Fields type(s) in use');
$field_storage->delete();
$this->drupalGet('admin/modules/uninstall');
$this->assertText('Fields pending deletion');
$this->cronRun();
$this->assertNoText('Fields type(s) in use');
$this->assertNoText('Fields pending deletion');
}
}