Skip to content
Snippets Groups Projects
Commit 42479a60 authored by Elliot Ward's avatar Elliot Ward
Browse files

#3092722: :sparkles: new plain text formatter

parent a2fd2202
No related branches found
No related tags found
1 merge request!8#3092722: ✨ new plain text formatter
...@@ -5,6 +5,16 @@ field.storage_settings.typed_resource_object: ...@@ -5,6 +5,16 @@ field.storage_settings.typed_resource_object:
resource_object_type: resource_object_type:
type: string type: string
label: 'Resource Object Type' label: 'Resource Object Type'
field.formatter.settings.typed_resource_object_string:
type: mapping
label: 'Typed resource object string settings'
mapping:
display_style:
type: string
label: 'Display style'
attribute:
type: string
label: 'Attribute'
jsonapi_reference.settings: jsonapi_reference.settings:
type: config_object type: config_object
label: 'JSON:API reference settings' label: 'JSON:API reference settings'
......
<?php
/**
* @file
* Contains hook_post_update_NAME() implementations.
*/
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
/**
* Implements hook_post_update_NAME().
*
* Searches all entity bundles for fields implementing of type typed resource
* object. For each, updates the view modes converting formatter type from
* text_default to basic_string.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
function jsonapi_reference_post_update_legacy_field_formatter_settings(&$sandbox) {
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
$entityTypeManager = \Drupal::service('entity_type.manager');
$article_node_type = $entityTypeManager->getStorage('node_type')->load('article');
// Get all fieldable entity definitions.
$entityTypeDefinitions = array_filter(
$entityTypeManager->getDefinitions(),
function (EntityTypeInterface $entityType) {
return $entityType->entityClassImplements(FieldableEntityInterface::class);
}
);
/** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo */
$entityTypeBundleInfo = \Drupal::service('entity_type.bundle.info');
// Get the bundle information for all fieldable entity definitions.
$entityBundleMap = [];
foreach ($entityTypeDefinitions as $entityTypeDefinition) {
$bundleInfo = $entityTypeBundleInfo->getBundleInfo($entityTypeDefinition->id());
$entityBundleMap[$entityTypeDefinition->id()] = array_keys($bundleInfo);
}
$fieldConfigStorage = $entityTypeManager->getStorage('field_config');
$entityUpdateMap = [];
// Look through all the bundles for typed_resource_object fields.
foreach ($entityBundleMap as $entity => $bundles) {
foreach ($bundles as $bundle) {
$bundleFieldConfig = $fieldConfigStorage->loadByProperties([
'entity_type' => $entity,
'bundle' => $bundle,
]);
/** @var \Drupal\field\Entity\FieldConfig $fieldConfig */
foreach ($bundleFieldConfig as $fieldConfig) {
if ($fieldConfig->getType() != 'typed_resource_object') {
// We don't care about this field.
continue;
}
// This entity & bundle combination has a typed_resource_object field.
// We need to adjust any display settings for it. For now, remember the
// entity, bundle, and field.
if (!array_key_exists($entity, $entityUpdateMap)) {
$entityUpdateMap[$entity] = [];
}
if (!array_key_exists($bundle, $entityUpdateMap[$entity])) {
$entityUpdateMap[$entity][$bundle] = [];
}
$entityUpdateMap[$entity][$bundle][] = $fieldConfig->getName();
}
}
}
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entityDisplayRepository */
$entityDisplayRepository = \Drupal::service('entity_display.repository');
foreach ($entityUpdateMap as $entity => $bundleFieldMap) {
$viewModes = [EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE];
$viewModes = array_merge($viewModes, array_keys($entityDisplayRepository->getViewModes($entity)));
foreach ($bundleFieldMap as $bundle => $fields) {
foreach ($viewModes as $viewName) {
$viewDisplay = $entityDisplayRepository->getViewDisplay($entity, $bundle, $viewName);
$displayChanged = FALSE;
foreach ($fields as $field) {
if ($component = $viewDisplay->getComponent($field)) {
if ($component['type'] == 'basic_string') {
$component['type'] = 'text_default';
$viewDisplay->setComponent($field, $component);
$displayChanged = TRUE;
}
}
}
if ($displayChanged) {
$viewDisplay->save();
}
}
}
}
}
<?php
namespace Drupal\jsonapi_reference\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\jsonapi_reference\Plugin\Field\FieldType\TypedResourceObjectItem;
use Drupal\jsonapi_reference\JsonApiClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of a 'basic_string' formatter.
*
* @FieldFormatter(
* id = "typed_resource_object_string",
* label = @Translation("JSON:API attribute"),
* field_types = {
* "typed_resource_object"
* }
* )
*/
class TypedResourceObjectStringFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
/**
* The JSON:API reference client.
*
* @var \Drupal\jsonapi_reference\JsonApiClientInterface
*/
protected $jsonapiClient;
/**
* Constructs a new TypedResourceObjectStringFormatter.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
* @param array $third_party_settings
* Third party settings.
* @param \Drupal\jsonapi_reference\JsonApiClientInterface $jsonapi_client
* The JSON:API reference client.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, JsonApiClientInterface $jsonapi_client) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
$this->jsonapiClient = $jsonapi_client;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['label'],
$configuration['view_mode'],
$configuration['third_party_settings'],
$container->get('jsonapi_reference.jsonapi_client')
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'display_style' => 'label',
'attribute' => 'title',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['display_style'] = [
'#type' => 'select',
'#title' => $this->t('Display style'),
'#options' => $this->getResourceObjectDisplayOptions(),
'#default_value' => $this->getSetting('display_style'),
'#required' => TRUE,
];
$elements['attribute'] = [
'#type' => 'textfield',
'#title' => $this->t('Attribute'),
'#default_value' => $this->getSetting('attribute'),
'#description' => $this->t('The attribute in the JSON:API resource object to display.'),
'#required' => TRUE,
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$display_styles = $this->getResourceObjectDisplayOptions();
$summary[] = $this->t('Display style: @display_style', ['@display_style' => $display_styles[$this->getSetting('display_style')]]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
$display_style = $this->getSetting('display_style');
// Load the field settings to determine how the field should be displayed.
foreach ($items as $delta => $item) {
$elements[$delta] = $this->formatDisplayStyle($item, $display_style);
}
return $elements;
}
/**
* Return an array of available string-based display styles for the field.
*
* @return array
* The array of available field display styles.
*/
protected function getResourceObjectDisplayOptions() {
return [
'label' => $this->t('Attribute'),
'label_id' => $this->t('Attribute (ID)'),
];
}
/**
* Format the output for the given display style.
*
* To override this function and provide a custom string-based display style,
* remember to call `parent::formatDisplayStyle`.
*
* @param \Drupal\jsonapi_reference\Plugin\Field\FieldType\TypedResourceObjectItem $item
* The field item.
* @param string $display_style
* The machine-readable display style.
*
* @return array
* The element's output.
*/
protected function formatDisplayStyle(TypedResourceObjectItem $item, string $display_style) {
$response = $this->getResourceObjectResponse($item);
switch ($display_style) {
case 'label':
if (isset($response->data->attributes->{$this->getSetting('attribute')})) {
$element = [
'#type' => 'inline_template',
'#template' => '{{ value|nl2br }}',
'#context' => ['value' => $response->data->attributes->{$this->getSetting('attribute')}],
];
}
else {
$element = $this->defaultDisplayStyleValue($item);
}
break;
case 'label_id':
if (isset($response->data->attributes->{$this->getSetting('attribute')})) {
$element = [
'#type' => 'inline_template',
'#template' => '{{ value|nl2br }}',
'#context' => [
'value' => $this->t('@title (@id)', [
'@title' => $response->data->attributes->{$this->getSetting('attribute')},
'@id' => $item->value,
]),
],
];
}
else {
$element = $this->defaultDisplayStyleValue($item);
}
break;
default:
$element = $this->defaultDisplayStyleValue($item);
break;
}
return $element;
}
/**
* Provide the default display style value.
*
* @param \Drupal\jsonapi_reference\Plugin\Field\FieldType\TypedResourceObjectItem $item
* The field item.
*
* @return array
* The default display style array.
*/
protected function defaultDisplayStyleValue(TypedResourceObjectItem $item) {
return [
'#type' => 'inline_template',
'#template' => '{{ value|nl2br }}',
'#context' => ['value' => $item->value],
];
}
/**
* Use the JSON:API reference client to retrieve data.
*
* @param \Drupal\jsonapi_reference\Plugin\Field\FieldType\TypedResourceObjectItem $item
* The field item.
*
* @return object|null
* See \Drupal\jsonapi_reference\JsonApiClientInterface::searchById().
*/
protected function getResourceObjectResponse(TypedResourceObjectItem $item) {
$object_type = $item->getFieldDefinition()->getSetting('resource_object_type');
return $this->jsonapiClient->searchById($object_type, $item->value);
}
}
...@@ -17,7 +17,7 @@ use Drupal\Core\TypedData\DataDefinition; ...@@ -17,7 +17,7 @@ use Drupal\Core\TypedData\DataDefinition;
* label = @Translation("Typed resource object"), * label = @Translation("Typed resource object"),
* description = @Translation("Field to represent a resource object in a remote system with a specific type."), * description = @Translation("Field to represent a resource object in a remote system with a specific type."),
* category = @Translation("Reference"), * category = @Translation("Reference"),
* default_formatter = "basic_string", * default_formatter = "typed_resource_object_string",
* default_widget = "typed_resource_object_autocomplete" * default_widget = "typed_resource_object_autocomplete"
* ) * )
*/ */
......
<?php
namespace Drupal\Tests\jsonapi_reference\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Class JsonAiReferenceFieldBase.
*
* Provides base functionality to perform various tests with the
* object_type_resource field type.
*/
abstract class JsonApiReferenceFieldBase extends BrowserTestBase {
use SchemaCheckTestTrait, StringTranslationTrait;
/**
* {@inheritdoc}
*/
static protected $modules = [
'jsonapi_reference',
'field_ui',
'node',
'jsonapi',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The name of the test content type.
*
* @var string
*/
protected $testContentType = 'test_content_type';
/**
* The machine name of the field type.
*
* @var string
*/
protected $fieldType = 'typed_resource_object';
/**
* The field config.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $fieldDefinition;
/**
* The name of the field to test.
*
* @var string
*/
protected $fieldName;
/**
* A user with administrative capabilities.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
\Drupal::service('config.storage')
->write('jsonapi_reference.settings', [
'endpoint' => 'http://localhost/jsonapi',
'username' => '',
'password' => '',
]);
// Create a content type to test with.
$this->contentType = $this->drupalCreateContentType(['type' => $this->testContentType]);
// Rebuild the routes before performing any tests to ensure the JSON:API
// dynamic routes function correctly.
\Drupal::service('router.builder')->rebuild();
// Generate a random field name to test the typed_resource_object field
// type. The field is then created using the generated name and the config
// is asserted as existing.
$this->fieldName = 'field_' . mb_strtolower($this->randomMachineName());
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
$field_storage = FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => $this->fieldType,
'settings' => [
'resource_object_type' => 'node--' . $this->testContentType,
],
]);
$field_storage->save();
/** @var \Drupal\field\Entity\FieldConfig $field */
$this->fieldDefinition = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $this->testContentType,
]);
$this->fieldDefinition->save();
$this->setDisplaySettings();
$field_config = $this->config('field.field.node.' . $this->testContentType . '.' . $this->fieldName);
$this->assertConfigSchema(\Drupal::service('config.typed'), $field_config->getName(), $field_config->get());
$field_storage_config = $this->config('field.storage.node.' . $this->fieldName);
$this->assertConfigSchema(\Drupal::service('config.typed'), $field_storage_config->getName(), $field_storage_config->get());
// Create an admin user capable of configuring and creating a field using
// the 'Typed resource object' field type.
$this->adminUser = $this->createUser([
'configure jsonapi reference',
'access content',
'administer node fields',
'administer node form display',
'administer content types',
'bypass node access',
]);
$this->drupalLogin($this->adminUser);
}
/**
* Set the display style of the field.
*
* @param string $display_style
* The display style to set.
*/
protected function setDisplaySettings(string $display_style = 'label', string $attribute = 'title') {
\Drupal::entityTypeManager()
->getStorage('entity_view_display')
->load('node.' . $this->testContentType . '.default')
->setComponent($this->fieldName, [
'type' => 'typed_resource_object_string',
'label' => 'above',
'settings' => [
'display_style' => $display_style,
'attribute' => $attribute,
],
'third_party_settings' => [],
'region' => 'content',
'weight' => 1,
])
->save();
}
}
<?php
namespace Drupal\Tests\jsonapi_reference\Functional;
/**
* Class JsonApiReferenceFieldFormatterTest.
*
* @group jsonapi_reference.
*/
class JsonApiReferenceFieldFormatterTest extends JsonApiReferenceFieldBase {
/**
* The item of content to reference.
*
* @var \Drupal\node\NodeInterface
*/
protected $referencedContent;
/**
* The item of content referencing $referencedContent.
*
* @var \Drupal\node\NodeInterface
*/
protected $referenceContent;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create an item of content to be referenced.
$this->referencedContent = $this->createNode([
'type' => $this->testContentType,
]);
$this->referenceContent = $this->createNode([
'type' => $this->testContentType,
$this->fieldName => ['value' => $this->referencedContent->uuid()],
]);
}
/**
* Test the 'GUID' display style.
*/
public function testGuid() {
$this->setDisplaySettings('guid');
$this->drupalGet('node/' . $this->referenceContent->id());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseContains($this->referencedContent->uuid());
$this->drupalLogout();
}
/**
* Test the 'Label' display style.
*/
public function testLabel() {
$this->setDisplaySettings('label');
$this->drupalGet('node/' . $this->referenceContent->id());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseContains($this->referencedContent->label());
$this->drupalLogout();
}
/**
* Test the 'Label (GUID)' display style.
*/
public function testLabelGuid() {
$this->setDisplaySettings('label_id');
$this->drupalGet('node/' . $this->referenceContent->id());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseContains($this->referencedContent->label() . ' (' . $this->referencedContent->uuid() . ')');
$this->drupalLogout();
}
/**
* Test the formatter setting 'label_attribute'.
*/
public function testLabelAttributeSetting() {
$this->setDisplaySettings('label', 'title');
$this->drupalGet('node/' . $this->referenceContent->id());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseContains("<div>{$this->referencedContent->getTitle()}</div>");
$this->setDisplaySettings('label', 'drupal_internal__nid');
$this->drupalGet('node/' . $this->referenceContent->id());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseContains("<div>{$this->referencedContent->id()}</div>");
$this->drupalLogout();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment