pluginreference-2.0.0/src/Plugin/Field/FieldType/PluginReferenceItem.php
src/Plugin/Field/FieldType/PluginReferenceItem.php
<?php
namespace Drupal\pluginreference\Plugin\Field\FieldType;
use Drupal\Component\Plugin\ConfigurableInterface;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\MapDataDefinition;
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\Core\Validation\Plugin\Validation\Constraint\AllowedValuesConstraint;
use Drupal\pluginreference\PluginReferenceSelectionInterface;
/**
* Plugin implementation of the 'plugin_reference' entity field type.
*/
#[FieldType(
id: 'plugin_reference',
label: new TranslatableMarkup('Plugin reference'),
description: new TranslatableMarkup('This field stores a soft reference to a plugin.'),
category: 'plugin_reference',
default_widget: 'plugin_reference_select',
default_formatter: 'plugin_reference_id',
list_class: '\Drupal\pluginreference\Plugin\Field\FieldType\PluginReferenceFieldItemList',
)]
class PluginReferenceItem extends FieldItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
return [
'target_type' => '',
] + parent::defaultStorageSettings();
}
/**
* {@inheritdoc}
*/
public static function defaultFieldSettings() {
return [
'handler' => 'default',
'handler_settings' => [],
] + parent::defaultFieldSettings();
}
/**
* {@inheritdoc}
*/
public static function mainPropertyName() {
return 'plugin_id';
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
// Prevent early t() calls by using the TranslatableMarkup.
$properties['plugin_id'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('Text value'))
->addConstraint('Length', ['max' => 255])
->setRequired(TRUE);
$properties['configuration'] = MapDataDefinition::create()
->setLabel(t('Configuration'));
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$schema = [
'columns' => [
'plugin_id' => [
'type' => 'varchar',
'length' => 255,
],
'configuration' => [
'description' => 'Serialized array of plugin configuration.',
'type' => 'blob',
'size' => 'big',
'serialize' => TRUE,
],
],
'indexes' => ['plugin_id' => ['plugin_id']],
];
return $schema;
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$values = [];
$plugin_manager = \Drupal::service('plugin_reference.plugin_type_helper')->getPluginManager($field_definition->getSetting('target_type'));
if ($plugin_manager instanceof PluginManagerInterface) {
$definitions = $plugin_manager->getDefinitions();
shuffle($definitions);
$values['plugin_id'] = key($definitions);
$values['configuration'] = [];
}
return $values;
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$elements = [];
$elements['target_type'] = [
'#type' => 'select',
'#title' => new TranslatableMarkup('Type of plugin to reference'),
'#options' => \Drupal::service('plugin_reference.plugin_type_helper')->getPluginTypeOptions(),
'#default_value' => $this->getSetting('target_type'),
'#required' => TRUE,
'#disabled' => $has_data,
'#size' => 1,
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
$form_object = $form_state->getFormObject();
/** @var \Drupal\field\FieldConfigInterface $field */
$field = $form_object->getEntity();
// Get all selection plugins for this plugin type.
$plugin_reference_selection_manager = \Drupal::service('plugin.manager.plugin_reference_selection');
$selection_plugins = $plugin_reference_selection_manager->getSelectionGroups($this->getSetting('target_type'));
$handlers_options = [];
foreach (array_keys($selection_plugins) as $selection_group_id) {
// We only display base plugins (e.g. 'default', 'views', ...) and not
// entity type specific plugins (e.g. 'default:node', 'default:user',
// ...).
if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) {
$handlers_options[$selection_group_id] = Html::escape($selection_plugins[$selection_group_id][$selection_group_id]['label']);
}
elseif (array_key_exists($selection_group_id . ':' . $this->getSetting('target_type'), $selection_plugins[$selection_group_id])) {
$selection_group_plugin = $selection_group_id . ':' . $this->getSetting('target_type');
$handlers_options[$selection_group_plugin] = Html::escape($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']);
}
}
$form = [
'#type' => 'container',
'#process' => [[static::class, 'fieldSettingsAjaxProcess']],
'#element_validate' => [[static::class, 'fieldSettingsFormValidate']],
];
$form['handler'] = [
'#type' => 'details',
'#title' => t('Reference type'),
'#open' => TRUE,
'#tree' => TRUE,
'#process' => [[static::class, 'formProcessMergeParent']],
];
$form['handler']['handler'] = [
'#type' => 'select',
'#title' => t('Reference method'),
'#options' => $handlers_options,
'#default_value' => $field->getSetting('handler'),
'#required' => TRUE,
'#ajax' => TRUE,
'#limit_validation_errors' => [],
];
$form['handler']['handler_submit'] = [
'#type' => 'submit',
'#value' => t('Change handler'),
'#limit_validation_errors' => [],
'#attributes' => [
'class' => ['js-hide'],
],
'#submit' => [[static::class, 'settingsAjaxSubmit']],
];
$form['handler']['handler_settings'] = [
'#type' => 'container',
'#attributes' => ['class' => ['plugin_reference-settings']],
];
$handler = $plugin_reference_selection_manager->getSelectionHandler($field);
$form['handler']['handler_settings'] += $handler->buildConfigurationForm([], $form_state);
return $form;
}
/**
* Form element validation handler; Invokes selection plugin's validation.
*
* @param array $form
* The form where the settings form is being included in.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the (entire) configuration form.
*/
public static function fieldSettingsFormValidate(array $form, FormStateInterface $form_state): void {
/** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
$form_object = $form_state->getFormObject();
$field = $form_object->getEntity();
$handler = \Drupal::service('plugin.manager.plugin_reference_selection')->getSelectionHandler($field);
$handler->validateConfigurationForm($form, $form_state);
}
/**
* Render API callback.
*
* Processes the field settings form and allows access to the form state.
*
* @see static::fieldSettingsForm()
*/
public static function fieldSettingsAjaxProcess(array $form, FormStateInterface $form_state): array {
static::fieldSettingsAjaxProcessElement($form, $form);
return $form;
}
/**
* Adds plugin reference specific properties to AJAX form elements.
*
* @see static::fieldSettingsAjaxProcess()
*/
public static function fieldSettingsAjaxProcessElement(array &$element, array $main_form): void {
if (!empty($element['#ajax'])) {
$element['#ajax'] = [
'callback' => [static::class, 'settingsAjax'],
'wrapper' => $main_form['#id'],
'element' => $main_form['#array_parents'],
];
}
foreach (Element::children($element) as $key) {
static::fieldSettingsAjaxProcessElement($element[$key], $main_form);
}
}
/**
* Render API callback.
*
* Moves plugin reference specific Form API elements(i.e. 'handler_settings')
* up a level for easier processing by the validation and submission handlers.
*/
public static function formProcessMergeParent(array $element): array {
$parents = $element['#parents'];
array_pop($parents);
$element['#parents'] = $parents;
return $element;
}
/**
* Ajax callback for the handler settings form.
*
* @see static::fieldSettingsForm()
*/
public static function settingsAjax(array $form, FormStateInterface $form_state): array {
$triggering_element = (array) $form_state->getTriggeringElement();
return NestedArray::getValue($form, $triggering_element['#ajax']['element']);
}
/**
* Submit handler for the non-JS case.
*
* @see static::fieldSettingsForm()
*/
public static function settingsAjaxSubmit(array $form, FormStateInterface $form_state): void {
$form_state->setRebuild();
}
/**
* {@inheritdoc}
*/
public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
$dependencies = parent::calculateDependencies($field_definition);
/** @var \Drupal\pluginreference\PluginTypeHelperInterface $plugin_type_helper */
$plugin_type_helper = \Drupal::service('plugin_reference.plugin_type_helper');
$target_type = $field_definition->getFieldStorageDefinition()->getSetting('target_type');
$plugin_manager = $plugin_type_helper->getPluginManager($target_type);
if (!$plugin_manager instanceof PluginManagerInterface) {
return $dependencies;
}
// Depend on the modules defining the plugins stored as default value.
if ($default_value = $field_definition->getDefaultValueLiteral()) {
foreach ($default_value as $value) {
if (is_array($value) && isset($value['plugin_id']) && $plugin_manager->hasDefinition($value['plugin_id'])) {
$plugin_definition = $plugin_manager->getDefinition($value['plugin_id']);
$dependencies['module'][] = $plugin_definition['provider'];
}
}
}
// Depend on the module providing the selection handler and the
// dependencies provided by the selection handler.
$selection_handler = \Drupal::service('plugin.manager.plugin_reference_selection')->getSelectionHandler($field_definition);
if ($selection_handler instanceof PluginReferenceSelectionInterface) {
$dependencies['module'][] = $selection_handler->getPluginDefinition()['provider'];
$dependencies = NestedArray::mergeDeep($dependencies, $selection_handler->calculateDependencies());
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) {
$dependencies = parent::calculateStorageDependencies($field_definition);
// Add a dependency on the module providing the plugin manager service.
$target_type = $field_definition->getSetting('target_type');
/** @var \Drupal\pluginreference\PluginTypeHelperInterface $plugin_type_helper */
$plugin_type_helper = \Drupal::service('plugin_reference.plugin_type_helper');
$provider = $plugin_type_helper->getPluginTypeProvider($target_type);
if ($provider !== NULL) {
$dependencies['module'][] = $provider;
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) {
/** @var \Drupal\Core\Field\FieldConfigInterface $field_definition */
$changed = parent::onDependencyRemoval($field_definition, $dependencies);
/** @var \Drupal\pluginreference\PluginTypeHelperInterface $plugin_type_helper */
$plugin_type_helper = \Drupal::service('plugin_reference.plugin_type_helper');
$target_type = $field_definition->getFieldStorageDefinition()->getSetting('target_type');
$plugin_manager = $plugin_type_helper->getPluginManager($target_type);
if (!$plugin_manager instanceof PluginManagerInterface) {
return FALSE;
}
// When config dependencies are removed, check if a module is removed
// that provides on of the default values. When that's the case, remove
// that default value from the field config.
if ($default_value = $field_definition->getDefaultValueLiteral()) {
foreach ($default_value as $key => $value) {
if (is_array($value) && isset($value['plugin_id'])) {
$plugin_definition = $plugin_manager->getDefinition($value['plugin_id']);
if ($plugin_definition && isset($dependencies['module']) && in_array($plugin_definition['provider'], $dependencies['module'], TRUE)) {
unset($default_value[$key]);
$changed = TRUE;
}
}
}
if ($changed) {
// Pass the values through array_values so the delta's are reset.
$field_definition->setDefaultValue(array_values($default_value));
}
}
$selection_manager_changed = FALSE;
$selection_handler_changed = FALSE;
/** @var \Drupal\pluginreference\PluginReferenceSelectionManagerInterface $selection_manager */
$selection_manager = \Drupal::service('plugin.manager.plugin_reference_selection');
$selection_handler = $selection_manager->getSelectionHandler($field_definition);
if ($selection_handler instanceof PluginReferenceSelectionInterface) {
$provider = $selection_handler->getPluginDefinition()['provider'];
// When the module, providing the selection handler, is removed, fallback
// to the default handler.
if (isset($dependencies['module']) && in_array($provider, $dependencies['module'], TRUE)) {
/** @var \Drupal\pluginreference\PluginReferenceSelectionInterface $selection_handler */
$selection_handler = $selection_manager->getInstance([
'target_type' => $target_type,
'handler' => 'default:' . $target_type,
]);
$field_definition->setSetting('handler', $selection_handler->getPluginId());
$default_configuration = $selection_handler->defaultConfiguration();
unset($default_configuration['entity'], $default_configuration['target_type']);
$field_definition->setSetting('handler_settings', $default_configuration);
$selection_manager_changed = TRUE;
}
// Handle dependency removal for the selection handler.
$selection_handler_changed = $selection_handler->onDependencyRemoval($field_definition, $dependencies);
}
$changed |= $selection_manager_changed | $selection_handler_changed;
return (bool) $changed;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$plugin_id = $this->get('plugin_id')->getValue();
$plugin_manager = \Drupal::service('plugin_reference.plugin_type_helper')->getPluginManager($this->getFieldDefinition()->getSetting('target_type'));
return empty($plugin_id) || !$plugin_manager instanceof PluginManagerInterface || !$plugin_manager->hasDefinition($plugin_id);
}
/**
* {@inheritdoc}
*/
public function getConstraints() {
$constraints = parent::getConstraints();
// Remove the 'AllowedValuesConstraint' validation constraint because plugin
// reference fields already use the 'ValidPluginReference' constraint.
foreach ($constraints as $key => $constraint) {
if ($constraint instanceof AllowedValuesConstraint) {
unset($constraints[$key]);
}
}
return $constraints;
}
/**
* {@inheritdoc}
*/
public function setValue($values, $notify = TRUE): void {
if ($values instanceof PluginInspectionInterface) {
$values = [
static::mainPropertyName() => $values->getPluginId(),
'configuration' => $values instanceof ConfigurableInterface ? $values->getConfiguration() : [],
];
}
// Treat the values as property value of the main property, if no array is
// given.
if (!is_array($values)) {
$values = [static::mainPropertyName() => $values];
}
// Unserialize the values.
if (isset($values['configuration']) && is_string($values['configuration'])) {
$values['configuration'] = unserialize($values['configuration']);
}
// Set the default value as an empty array.
if (!isset($values['configuration']) || empty($values['configuration'])) {
$values['configuration'] = [];
}
parent::setValue($values, $notify);
}
/**
* Get the initialized plugin.
*
* @return \Drupal\Component\Plugin\PluginBase|null
* The initialized plugin.
*/
public function referencedPlugin(): ?PluginBase {
if ($this->isEmpty()) {
return NULL;
}
$plugin_id = $this->get('plugin_id')->getValue();
$configuration = $this->get('configuration')->getValue() ?? [];
$plugin_manager = \Drupal::service('plugin_reference.plugin_type_helper')->getPluginManager($this->getFieldDefinition()->getSetting('target_type'));
return $plugin_manager->createInstance($plugin_id, $configuration);
}
/**
* {@inheritdoc}
*/
public static function getPreconfiguredOptions() {
$options = [];
/** @var \Drupal\pluginreference\PluginTypeHelperInterface $plugin_type_helper */
$plugin_type_helper = \Drupal::service('plugin_reference.plugin_type_helper');
$plugin_references = $plugin_type_helper->getPluginTypeIds();
foreach ($plugin_references as $plugin_type_id) {
if ($plugin_type_helper->pluginTypeExists($plugin_type_id)) {
$options[$plugin_type_id] = [
'label' => $plugin_type_helper->getPluginTypeName($plugin_type_id),
'field_storage_config' => [
'settings' => [
'target_type' => $plugin_type_id,
],
],
];
}
}
return $options;
}
/**
* {@inheritdoc}
*/
public function getPossibleValues(?AccountInterface $account = NULL) {
return $this->getSettableValues($account);
}
/**
* {@inheritdoc}
*/
public function getPossibleOptions(?AccountInterface $account = NULL) {
return $this->getSettableOptions($account);
}
/**
* {@inheritdoc}
*/
public function getSettableValues(?AccountInterface $account = NULL) {
return array_keys($this->getSettableOptions($account));
}
/**
* {@inheritdoc}
*/
public function getSettableOptions(?AccountInterface $account = NULL) {
$field_definition = $this->getFieldDefinition();
if (!$options = \Drupal::service('plugin.manager.plugin_reference_selection')->getSelectionHandler($field_definition, $this->getEntity())->getReferenceablePlugins()) {
return [];
}
return $options;
}
}
