예제 #1
0
/**
 * Forbid a field storage update from occurring.
 *
 * Any module may forbid any update for any reason. For example, the
 * field's storage module might forbid an update if it would change
 * the storage schema while data for the field exists. A field type
 * module might forbid an update if it would change existing data's
 * semantics, or if there are external dependencies on field settings
 * that cannot be updated.
 *
 * To forbid the update from occurring, throw a
 * \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException.
 *
 * @param \Drupal\field\FieldStorageConfigInterface $field_storage
 *   The field storage as it will be post-update.
 * @param \Drupal\field\FieldStorageConfigInterface $prior_field_storage
 *   The field storage as it is pre-update.
 *
 * @see entity_crud
 */
function hook_field_storage_config_update_forbid(\Drupal\field\FieldStorageConfigInterface $field_storage, \Drupal\field\FieldStorageConfigInterface $prior_field_storage)
{
    if ($field_storage->module == 'options' && $field_storage->hasData()) {
        // Forbid any update that removes allowed values with actual data.
        $allowed_values = $field_storage->getSetting('allowed_values');
        $prior_allowed_values = $prior_field_storage->getSetting('allowed_values');
        $lost_keys = array_keys(array_diff_key($prior_allowed_values, $allowed_values));
        if (_options_values_in_use($field_storage->getTargetEntityTypeId(), $field_storage->getName(), $lost_keys)) {
            throw new \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException(t('A list field (@field_name) with existing data cannot have its keys changed.', array('@field_name' => $field_storage->getName())));
        }
    }
}
 /**
  * {@inheritdoc}
  */
 public function setUp()
 {
     parent::setUp();
     $this->installEntitySchema('entity_test_rev');
     $this->installEntitySchema('user');
     $field_name = 'test';
     $this->fieldStorage = FieldStorageConfig::create(['field_name' => $field_name, 'entity_type' => 'entity_test_rev', 'type' => 'string', 'cardinality' => 1]);
     $this->fieldStorage->save();
     $this->field = FieldConfig::create(['field_name' => $field_name, 'entity_type' => 'entity_test_rev', 'bundle' => 'entity_test_rev', 'required' => TRUE]);
     $this->field->save();
     // Create an entity with field data.
     $this->entity = EntityTestRev::create(['user_id' => mt_rand(1, 10), 'name' => $this->randomMachineName(), $field_name => $this->randomString()]);
     $this->entity->save();
 }
 /**
  * @covers ::getType
  */
 public function testGetType()
 {
     // Ensure that FieldConfig::getType() is not delegated to
     // FieldStorage.
     $this->entityManager->expects($this->never())->method('getFieldStorageDefinitions');
     $this->fieldStorage->expects($this->never())->method('getType');
     $field = new FieldConfig(array('field_name' => $this->fieldStorage->getName(), 'entity_type' => 'test_entity_type', 'bundle' => 'test_bundle', 'field_type' => 'test_field'), $this->entityTypeId);
     $this->assertEquals('test_field', $field->getType());
 }
 /**
  * @covers ::toArray()
  */
 public function testToArray()
 {
     $values = array('field_name' => $this->fieldStorage->getName(), 'entity_type' => 'test_entity_type', 'bundle' => 'test_bundle');
     $instance = new FieldInstanceConfig($values, $this->entityTypeId);
     $expected = array('id' => 'test_entity_type.test_bundle.field_test', 'uuid' => NULL, 'status' => TRUE, 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'field_name' => 'field_test', 'entity_type' => 'test_entity_type', 'bundle' => 'test_bundle', 'label' => '', 'description' => '', 'required' => FALSE, 'default_value' => array(), 'default_value_function' => '', 'settings' => array(), 'dependencies' => array(), 'field_type' => 'test_field');
     $this->entityManager->expects($this->any())->method('getDefinition')->with($this->entityTypeId)->will($this->returnValue($this->entityType));
     $this->entityType->expects($this->once())->method('getKey')->with('id')->will($this->returnValue('id'));
     $this->typedConfigManager->expects($this->once())->method('getDefinition')->will($this->returnValue(array('mapping' => array_fill_keys(array_keys($expected), ''))));
     $export = $instance->toArray();
     $this->assertEquals($expected, $export);
 }
예제 #5
0
 protected function setUp()
 {
     parent::setUp();
     // Create two content types. One will have an autocomplete tagging field,
     // and one won't.
     $this->nodeTypeWithTags = $this->drupalCreateContentType();
     $this->nodeTypeWithoutTags = $this->drupalCreateContentType();
     // Create the vocabulary for the tag field.
     $this->tagVocabulary = entity_create('taxonomy_vocabulary', array('name' => 'Views testing tags', 'vid' => 'views_testing_tags'));
     $this->tagVocabulary->save();
     // Create the tag field itself.
     $this->tagFieldName = 'field_views_testing_tags';
     $this->tagFieldStorage = entity_create('field_storage_config', array('field_name' => $this->tagFieldName, 'entity_type' => 'node', 'type' => 'taxonomy_term_reference', 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, 'settings' => array('allowed_values' => array(array('vocabulary' => $this->tagVocabulary->id(), 'parent' => 0)))));
     $this->tagFieldStorage->save();
     // Create an instance of the tag field on one of the content types, and
     // configure it to display an autocomplete widget.
     $this->tagField = array('field_storage' => $this->tagFieldStorage, 'bundle' => $this->nodeTypeWithTags->id());
     entity_create('field_config', $this->tagField)->save();
     entity_get_form_display('node', $this->nodeTypeWithTags->id(), 'default')->setComponent('field_views_testing_tags', array('type' => 'taxonomy_autocomplete'))->save();
     entity_get_display('node', $this->nodeTypeWithTags->id(), 'default')->setComponent('field_views_testing_tags', array('type' => 'taxonomy_term_reference_link', 'weight' => 10))->save();
     entity_get_display('node', $this->nodeTypeWithTags->id(), 'teaser')->setComponent('field_views_testing_tags', array('type' => 'taxonomy_term_reference_link', 'weight' => 10))->save();
 }
 /**
  * Tests that vocabulary machine name changes are mirrored in field definitions.
  */
 function testTaxonomyTermFieldChangeMachineName()
 {
     // Add several entries in the 'allowed_values' setting, to make sure that
     // they all get updated.
     $this->field_storage->settings['allowed_values'] = array(array('vocabulary' => $this->vocabulary->id(), 'parent' => '0'), array('vocabulary' => $this->vocabulary->id(), 'parent' => '0'), array('vocabulary' => 'foo', 'parent' => '0'));
     $this->field_storage->save();
     // Change the machine name.
     $new_name = drupal_strtolower($this->randomMachineName());
     $this->vocabulary->vid = $new_name;
     $this->vocabulary->save();
     // Check that the field is still attached to the vocabulary.
     $field_storage = FieldStorageConfig::loadByName('entity_test', $this->field_name);
     $allowed_values = $field_storage->getSetting('allowed_values');
     $this->assertEqual($allowed_values[0]['vocabulary'], $new_name, 'Index 0: Machine name was updated correctly.');
     $this->assertEqual($allowed_values[1]['vocabulary'], $new_name, 'Index 1: Machine name was updated correctly.');
     $this->assertEqual($allowed_values[2]['vocabulary'], 'foo', 'Index 2: Machine name was left untouched.');
 }
 /**
  * {@inheritdoc}
  */
 public function save(array $form, FormStateInterface $form_state)
 {
     $field_label = $form_state->get('field_config')->label();
     try {
         $this->entity->save();
         drupal_set_message($this->t('Updated field %label field settings.', array('%label' => $field_label)));
         $request = $this->getRequest();
         if (($destinations = $request->query->get('destinations')) && ($next_destination = FieldUI::getNextDestination($destinations))) {
             $request->query->remove('destinations');
             $form_state->setRedirectUrl($next_destination);
         } else {
             $form_state->setRedirectUrl(FieldUI::getOverviewRouteInfo($form_state->get('entity_type_id'), $form_state->get('bundle')));
         }
     } catch (\Exception $e) {
         drupal_set_message($this->t('Attempt to update field %label failed: %message.', array('%label' => $field_label, '%message' => $e->getMessage())), 'error');
     }
 }
예제 #8
0
/**
 * Alter the Views data on a per field basis.
 *
 * The field module's implementation of hook_views_data_alter() invokes this for
 * each field storage, in the module that defines the field type. It is not
 * invoked in other modules.
 *
 * Unlike hook_field_views_data_alter(), this operates on the whole of the views
 * data. This allows a field type to add data that concerns its fields in
 * other tables, which would not yet be defined at the point when
 * hook_field_views_data() and hook_field_views_data_alter() are invoked. For
 * example, entityreference adds reverse relationships on the tables for the
 * entities which are referenced by entityreference fields.
 *
 * (Note: this is weirdly named so as not to conflict with
 * hook_field_views_data_alter().)
 *
 * @param array $data
 *   The views data.
 * @param \Drupal\field\FieldStorageConfigInterface $field
 *   The field storage config entity.
 *
 * @see hook_field_views_data()
 * @see hook_field_views_data_alter()
 * @see field_views_data_alter()
 */
function hook_field_views_data_views_data_alter(array &$data, \Drupal\field\FieldStorageConfigInterface $field)
{
    $field_name = $field->getName();
    $data_key = 'field_data_' . $field_name;
    $entity_type_id = $field->entity_type;
    $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
    $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type_id;
    list($label) = field_views_field_label($entity_type_id, $field_name);
    // Views data for this field is in $data[$data_key].
    $data[$data_key][$pseudo_field_name]['relationship'] = array('title' => t('@entity using @field', array('@entity' => $entity_type->getLabel(), '@field' => $label)), 'help' => t('Relate each @entity with a @field set to the term.', array('@entity' => $entity_type->getLabel(), '@field' => $label)), 'id' => 'entity_reverse', 'field_name' => $field_name, 'entity_type' => $entity_type_id, 'field table' => ContentEntityDatabaseStorage::_fieldTableName($field), 'field field' => $field_name . '_target_id', 'base' => $entity_type->getBaseTable(), 'base field' => $entity_type->getKey('id'), 'label' => t('!field_name', array('!field_name' => $field_name)), 'join_extra' => array(0 => array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE)));
}
예제 #9
0
/**
 * Forbid a field storage update from occurring.
 *
 * Any module may forbid any update for any reason. For example, the
 * field's storage module might forbid an update if it would change
 * the storage schema while data for the field exists. A field type
 * module might forbid an update if it would change existing data's
 * semantics, or if there are external dependencies on field settings
 * that cannot be updated.
 *
 * To forbid the update from occurring, throw a
 * \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException.
 *
 * @param \Drupal\field\FieldStorageConfigInterface $field_storage
 *   The field storage as it will be post-update.
 * @param \Drupal\field\FieldStorageConfigInterface $prior_field_storage
 *   The field storage as it is pre-update.
 *
 * @see entity_crud
 */
function hook_field_storage_config_update_forbid(\Drupal\field\FieldStorageConfigInterface $field_storage, \Drupal\field\FieldStorageConfigInterface $prior_field_storage)
{
    // A 'list' field stores integer keys mapped to display values. If
    // the new field will have fewer values, and any data exists for the
    // abandoned keys, the field will have no way to display them. So,
    // forbid such an update.
    if ($field_storage->hasData() && count($field_storage['settings']['allowed_values']) < count($prior_field_storage['settings']['allowed_values'])) {
        // Identify the keys that will be lost.
        $lost_keys = array_diff(array_keys($field_storage['settings']['allowed_values']), array_keys($prior_field_storage['settings']['allowed_values']));
        // If any data exist for those keys, forbid the update.
        $query = new EntityFieldQuery();
        $found = $query->fieldCondition($prior_field_storage['field_name'], 'value', $lost_keys)->range(0, 1)->execute();
        if ($found) {
            throw new \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException("Cannot update a list field storage not to include keys with existing data");
        }
    }
}
 /**
  * Tests a simple site export import case.
  */
 public function testExportImport()
 {
     // After installation there is no snapshot and nothing to import.
     $this->drupalGet('admin/config/development/configuration');
     $this->assertNoText(t('Warning message'));
     $this->assertText(t('There are no configuration changes to import.'));
     $this->originalSlogan = $this->config('system.site')->get('slogan');
     $this->newSlogan = $this->randomString(16);
     $this->assertNotEqual($this->newSlogan, $this->originalSlogan);
     $this->config('system.site')->set('slogan', $this->newSlogan)->save();
     $this->assertEqual($this->config('system.site')->get('slogan'), $this->newSlogan);
     // Create a content type.
     $this->contentType = $this->drupalCreateContentType();
     // Create a field.
     $this->fieldName = Unicode::strtolower($this->randomMachineName());
     $this->fieldStorage = entity_create('field_storage_config', array('field_name' => $this->fieldName, 'entity_type' => 'node', 'type' => 'text'));
     $this->fieldStorage->save();
     entity_create('field_config', array('field_storage' => $this->fieldStorage, 'bundle' => $this->contentType->id()))->save();
     // Update the displays so that configuration does not change unexpectedly on
     // import.
     entity_get_form_display('node', $this->contentType->id(), 'default')->setComponent($this->fieldName, array('type' => 'text_textfield'))->save();
     entity_get_display('node', $this->contentType->id(), 'full')->setComponent($this->fieldName)->save();
     entity_get_display('node', $this->contentType->id(), 'default')->setComponent($this->fieldName)->save();
     entity_get_display('node', $this->contentType->id(), 'teaser')->removeComponent($this->fieldName)->save();
     $this->drupalGet('node/add/' . $this->contentType->id());
     $this->assertFieldByName("{$this->fieldName}[0][value]", '', 'Widget is displayed');
     // Export the configuration.
     $this->drupalPostForm('admin/config/development/configuration/full/export', array(), 'Export');
     $this->tarball = $this->getRawContent();
     $this->config('system.site')->set('slogan', $this->originalSlogan)->save();
     $this->assertEqual($this->config('system.site')->get('slogan'), $this->originalSlogan);
     // Delete the custom field.
     $fields = FieldConfig::loadMultiple();
     foreach ($fields as $field) {
         if ($field->getName() == $this->fieldName) {
             $field->delete();
         }
     }
     $field_storages = FieldStorageConfig::loadMultiple();
     foreach ($field_storages as $field_storage) {
         if ($field_storage->getName() == $this->fieldName) {
             $field_storage->delete();
         }
     }
     $this->drupalGet('node/add/' . $this->contentType->id());
     $this->assertNoFieldByName("{$this->fieldName}[0][value]", '', 'Widget is not displayed');
     // Import the configuration.
     $filename = 'temporary://' . $this->randomMachineName();
     file_put_contents($filename, $this->tarball);
     $this->drupalPostForm('admin/config/development/configuration/full/import', array('files[import_tarball]' => $filename), 'Upload');
     // There is no snapshot yet because an import has never run.
     $this->assertNoText(t('Warning message'));
     $this->assertNoText(t('There are no configuration changes to import.'));
     $this->assertText($this->contentType->label());
     $this->drupalPostForm(NULL, array(), 'Import all');
     // After importing the snapshot has been updated an there are no warnings.
     $this->assertNoText(t('Warning message'));
     $this->assertText(t('There are no configuration changes to import.'));
     $this->assertEqual($this->config('system.site')->get('slogan'), $this->newSlogan);
     $this->drupalGet('node/add');
     $this->assertFieldByName("{$this->fieldName}[0][value]", '', 'Widget is displayed');
     $this->config('system.site')->set('slogan', $this->originalSlogan)->save();
     $this->drupalGet('admin/config/development/configuration');
     $this->assertText(t('Warning message'));
     $this->assertText('The following items in your active configuration have changes since the last import that may be lost on the next import.');
     // Ensure the item is displayed as part of a list (to avoid false matches
     // on the rest of the page) and that the list markup is not escaped.
     $this->assertRaw('<li>system.site</li>');
     // Remove everything from staging. The warning about differences between the
     // active and snapshot should no longer exist.
     \Drupal::service('config.storage.staging')->deleteAll();
     $this->drupalGet('admin/config/development/configuration');
     $this->assertNoText(t('Warning message'));
     $this->assertNoText('The following items in your active configuration have changes since the last import that may be lost on the next import.');
     $this->assertText(t('There are no configuration changes to import.'));
     // Write a file to staging. The warning about differences between the
     // active and snapshot should now exist.
     /** @var \Drupal\Core\Config\StorageInterface $staging */
     $staging = $this->container->get('config.storage.staging');
     $data = $this->config('system.site')->get();
     $data['slogan'] = 'in the face';
     $this->copyConfig($this->container->get('config.storage'), $staging);
     $staging->write('system.site', $data);
     $this->drupalGet('admin/config/development/configuration');
     $this->assertText(t('Warning message'));
     $this->assertText('The following items in your active configuration have changes since the last import that may be lost on the next import.');
     // Ensure the item is displayed as part of a list (to avoid false matches
     // on the rest of the page) and that the list markup is not escaped.
     $this->assertRaw('<li>system.site</li>');
 }
 /**
  * {@inheritdoc}
  */
 public function fieldGetPermissionType(FieldStorageConfigInterface $field)
 {
     $config = \Drupal::service('config.factory')->getEditable('field_permissions.field.settings');
     $field_settings_perm = $config->get('permission_type_' . $field->getName());
     return $field_settings_perm ? $field_settings_perm : FIELD_PERMISSIONS_PUBLIC;
 }
 /**
  * Tests a simple site export import case.
  */
 public function testExportImport()
 {
     // After installation there is no snapshot and nothing to import.
     $this->drupalGet('admin/config/development/configuration');
     $this->assertNoText(t('Warning message'));
     $this->assertText(t('There are no configuration changes to import.'));
     $this->originalSlogan = $this->config('system.site')->get('slogan');
     $this->newSlogan = $this->randomString(16);
     $this->assertNotEqual($this->newSlogan, $this->originalSlogan);
     $this->config('system.site')->set('slogan', $this->newSlogan)->save();
     $this->assertEqual($this->config('system.site')->get('slogan'), $this->newSlogan);
     // Create a content type.
     $this->contentType = $this->drupalCreateContentType();
     // Create a field.
     $this->fieldName = Unicode::strtolower($this->randomMachineName());
     $this->fieldStorage = entity_create('field_storage_config', array('field_name' => $this->fieldName, 'entity_type' => 'node', 'type' => 'text'));
     $this->fieldStorage->save();
     entity_create('field_config', array('field_storage' => $this->fieldStorage, 'bundle' => $this->contentType->id()))->save();
     // Update the displays so that configuration does not change unexpectedly on
     // import.
     entity_get_form_display('node', $this->contentType->id(), 'default')->setComponent($this->fieldName, array('type' => 'text_textfield'))->save();
     entity_get_display('node', $this->contentType->id(), 'full')->setComponent($this->fieldName)->save();
     entity_get_display('node', $this->contentType->id(), 'default')->setComponent($this->fieldName)->save();
     entity_get_display('node', $this->contentType->id(), 'teaser')->removeComponent($this->fieldName)->save();
     $this->drupalGet('node/add/' . $this->contentType->id());
     $this->assertFieldByName("{$this->fieldName}[0][value]", '', 'Widget is displayed');
     // Export the configuration.
     $this->drupalPostForm('admin/config/development/configuration/full/export', array(), 'Export');
     $this->tarball = $this->getRawContent();
     $this->config('system.site')->set('slogan', $this->originalSlogan)->save();
     $this->assertEqual($this->config('system.site')->get('slogan'), $this->originalSlogan);
     // Delete the custom field.
     $fields = FieldConfig::loadMultiple();
     foreach ($fields as $field) {
         if ($field->field_name == $this->fieldName) {
             $field->delete();
         }
     }
     $field_storages = FieldStorageConfig::loadMultiple();
     foreach ($field_storages as $field_storage) {
         if ($field_storage->getName() == $this->fieldName) {
             $field_storage->delete();
         }
     }
     $this->drupalGet('node/add/' . $this->contentType->id());
     $this->assertNoFieldByName("{$this->fieldName}[0][value]", '', 'Widget is not displayed');
     // Import the configuration.
     $filename = 'temporary://' . $this->randomMachineName();
     file_put_contents($filename, $this->tarball);
     $this->drupalPostForm('admin/config/development/configuration/full/import', array('files[import_tarball]' => $filename), 'Upload');
     // There is no snapshot yet because an import has never run.
     $this->assertNoText(t('Warning message'));
     $this->assertNoText(t('There are no configuration changes to import.'));
     $this->assertText($this->contentType->label());
     $this->drupalPostForm(NULL, array(), 'Import all');
     // After importing the snapshot has been updated an there are no warnings.
     $this->assertNoText(t('Warning message'));
     $this->assertText(t('There are no configuration changes to import.'));
     $this->assertEqual($this->config('system.site')->get('slogan'), $this->newSlogan);
     $this->drupalGet('node/add');
     $this->assertFieldByName("{$this->fieldName}[0][value]", '', 'Widget is displayed');
     $this->config('system.site')->set('slogan', $this->originalSlogan)->save();
     $this->drupalGet('admin/config/development/configuration');
     $this->assertText(t('Warning message'));
     $this->assertText('Your current configuration has changed. Changes to these configuration items will be lost on the next synchronization: system.site');
     // Remove everything from staging. The warning about differences between the
     // active and snapshot should still exist.
     \Drupal::service('config.storage.staging')->deleteAll();
     $this->drupalGet('admin/config/development/configuration');
     $this->assertText(t('Warning message'));
     $this->assertText('Your current configuration has changed. Changes to these configuration items will be lost on the next synchronization: system.site');
     $this->assertText(t('There are no configuration changes to import.'));
 }