admin view for taxo genres and langues, translation for taxo genres and langues, synonyms for genres

This commit is contained in:
Bachir Soussi Chiadmi
2018-04-11 16:23:58 +02:00
parent 1d3a3dd6fd
commit 07a138cfe6
81 changed files with 6114 additions and 67 deletions
@@ -0,0 +1,56 @@
<?php
namespace Drupal\synonyms\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Annotation for synonyms provider plugin instance.
*
* @Annotation
*/
class SynonymsProvider extends Plugin {
/**
* Machine readable name of this plugin,
*
* @var string
*/
public $id;
/**
* Human readable name of this plugin.
*
* @var string
*/
public $label;
/**
* Synonyms behavior service ID into which this plugin provides synonyms.
*
* Synonyms behaviors are the services with the tag 'synonyms_behavior'.
*
* @var string
*/
public $synonyms_behavior_service;
/**
* Entity type which is controlled by ths plugin.
*
* Entity type into which this plugin provides synonyms.
*
* @var string
*/
public $controlled_entity_type;
/**
* Bundle which is controlled by this plugin.
*
* Bundle into which this plugin provides synonyms. If the entity type does
* not support bundles, just put here the entity type.
*
* @var string
*/
public $controlled_bundle;
}
@@ -0,0 +1,66 @@
<?php
namespace Drupal\synonyms\Controller;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\synonyms\SynonymsService\Behavior\AutocompleteService;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Synonyms friendly entity autocomplete controller.
*/
class EntityAutocomplete extends ControllerBase {
/**
* @var AutocompleteService
*/
protected $autocompleteService;
/**
* EntityAutocomplete constructor.
*/
public function __construct(AutocompleteService $autocomplete_service) {
$this->autocompleteService = $autocomplete_service;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('synonyms.behavior.autocomplete')
);
}
/**
* Routing callback: provide suggestions for autocomplete form element.
*/
public function autocomplete(Request $request, EntityTypeInterface $target_type, $token) {
$matches = [];
if ($input = $request->get('q')) {
$input = Tags::explode($input);
$input = array_pop($input);
foreach ($this->autocompleteService->autocompleteLookup($input, $token) as $k => $suggestion) {
$key = $suggestion['entity_label'] . ' (' . $suggestion['entity_id'] . ')';
$key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(Html::decodeEntities(strip_tags($key)))));
// Names containing commas or quotes must be wrapped in quotes.
$key = Tags::encode($key);
$matches[] = [
'value' => $key,
'label' => $suggestion['wording'],
];
}
}
return new JsonResponse($matches);
}
}
@@ -0,0 +1,149 @@
<?php
namespace Drupal\synonyms\Controller;
use Drupal\Component\Utility\Html;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Url;
use Drupal\synonyms\SynonymsService\Behavior\SynonymsBehaviorInterface;
use Drupal\synonyms\SynonymsService\BehaviorService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Controller for admin UI of the module.
*/
class SynonymConfigController extends ControllerBase {
/**
* @var EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* SynonymConfigController constructor.
*/
public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_info, BehaviorService $behavior_service) {
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->behaviorService = $behavior_service;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.bundle.info'),
$container->get('synonyms.behaviors')
);
}
/**
* Routing callback: show overview table of synonyms configuration.
*/
public function overview() {
$render = [
'#type' => 'table',
'#header' => [
$this->t('Entity type'),
$this->t('Bundle'),
$this->t('Manage'),
],
];
foreach ($this->entityTypeManager()->getDefinitions() as $entity_type) {
if ($entity_type instanceof ContentEntityTypeInterface) {
foreach ($this->entityTypeBundleInfo->getBundleInfo($entity_type->id()) as $bundle => $bundle_info) {
$links = [];
foreach ($this->behaviorService->getBehaviorServices() as $service_id => $service) {
$count = count($this->behaviorService->getSynonymConfigEntities($service_id, $entity_type->id(), $bundle));
if ($count > 0) {
$title = $this->t('@behavior (@count)', [
'@behavior' => $service['service']->getTitle(),
'@count' => $count,
]);
}
else {
$title = $service['service']->getTitle();
}
$links[$service_id] = [
'title' => $title,
'url' => Url::fromRoute('entity.synonym.overview.entity_type.bundle.behavior', [
'synonyms_entity_type' => $entity_type->id(),
'bundle' => $bundle,
'synonyms_behavior_service' => $service_id,
]),
];
}
$render[] = [
['#markup' => Html::escape($entity_type->getLabel())],
['#markup' => $bundle == $entity_type->id() ? '' : Html::escape($bundle_info['label'])],
['#type' => 'operations', '#links' => $links],
];
}
}
}
return $render;
}
/**
* Routing callback to overview a particular entity type and behavior service.
*/
public function overviewEntityTypeBundle(EntityTypeInterface $synonyms_entity_type, $bundle, SynonymsBehaviorInterface $synonyms_behavior_service) {
$table = [
'#type' => 'table',
'#header' => [
$this->t('Provider'),
$this->t('Operations'),
],
];
foreach ($this->entityTypeManager()->getStorage('synonym')->loadMultiple() as $synonym_config) {
$provider_instance = $synonym_config->getProviderPluginInstance();
$provider_definition = $provider_instance->getPluginDefinition();
if ($provider_definition['controlled_entity_type'] == $synonyms_entity_type->id() && $provider_definition['controlled_bundle'] == $bundle && $this->behaviorService->getBehaviorService($provider_definition['synonyms_behavior_service'])['service'] == $synonyms_behavior_service) {
$table[] = [
['#markup' => Html::escape($synonym_config->label())],
[
'#type' => 'operations',
'#links' => $this->entityTypeManager()->getListBuilder($synonym_config->getEntityTypeId())->getOperations($synonym_config),
],
];
}
}
return $table;
}
/**
* Title callback for 'entity.synonym.overview.entity_type.bundle.behavior'.
*/
public function overviewEntityTypeBundleTitle(EntityTypeInterface $synonyms_entity_type, $bundle, SynonymsBehaviorInterface $synonyms_behavior_service) {
if ($synonyms_entity_type->id() == $bundle) {
return $this->t('Manage @behavior of @entity_type', [
'@behavior' => $synonyms_behavior_service->getTitle(),
'@entity_type' => $synonyms_entity_type->getLabel(),
]);
}
$bundle_info = $this->entityTypeBundleInfo->getBundleInfo($synonyms_entity_type->id());
return $this->t('Manage @behavior of @entity_type @bundle', [
'@behavior' => $synonyms_behavior_service->getTitle(),
'@entity_type' => $synonyms_entity_type->getLabel(),
'@bundle' => $bundle_info[$bundle]['label'],
]);
}
}
@@ -0,0 +1,148 @@
<?php
namespace Drupal\synonyms\Element;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Textfield;
use Drupal\Core\Site\Settings;
/**
* Form element for synonyms-friendly entity autocomplete.
*
* @FormElement("synonyms_entity_autocomplete")
*/
class SynonymsEntityAutocomplete extends Textfield {
/**
* {@inheritdoc}
*/
public function getInfo() {
$info = parent::getInfo();
// Target entity type for suggestions.
$info['#target_type'] = NULL;
// String or array of allowed target bundles. If omitted, all bundles will
// be included in autocomplete suggestions.
$info['#target_bundles'] = NULL;
// Default maximum amount of provided suggestions.
$info['#suggestion_size'] = 10;
// Whether to suggest same entity at most once (in the case, when more than
// 1 synonym triggers inclusion of that entity).
$info['#suggest_only_unique'] = FALSE;
// Operator to match keyword. Allowed values are:
// - CONTAINS
// - STARTS_WITH
$info['#match'] = 'CONTAINS';
array_unshift($info['#process'], [get_class($this), 'elementSynonymsEntityAutocomplete']);
$info['#element_validate'][] = [get_class($this), 'validateEntityAutocomplete'];
return $info;
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
$entities = NULL;
if ($input === FALSE) {
if (isset($element['#default_value'])) {
$entities = [];
foreach ($element['#default_value'] as $entity) {
$entities[] = $entity;
}
}
}
// Potentially the #value is set directly, so it contains the 'target_id'
// array structure instead of a string.
elseif ($input !== FALSE && is_array($input)) {
$entity_ids = array_map(function(array $item) {
return $item['target_id'];
}, $input);
$entities = \Drupal::entityTypeManager()->getStorage($element['#target_type'])->loadMultiple($entity_ids);
}
if (is_array($entities)) {
$value = [];
foreach ($entities as $entity) {
$value[] = $entity->label() . ' (' . $entity->id() . ')';
}
return Tags::implode($value);
}
}
/**
* Form element process callback for 'synonyms_entity_autocomplete' type.
*/
public static function elementSynonymsEntityAutocomplete(array &$element, FormStateInterface $form_state, array &$complete_form) {
$data = [
'target_type' => $element['#target_type'],
'target_bundles' => $element['#target_bundles'],
'suggestion_size' => $element['#suggestion_size'],
'suggest_only_unique' => $element['#suggest_only_unique'],
'match' => $element['#match'],
];
$token = Crypt::hmacBase64(serialize($data), Settings::getHashSalt());
$key_value_storage = \Drupal::keyValue('synonyms_entity_autocomplete');
$key_value_storage->setIfNotExists($token, $data);
$element['#autocomplete_route_name'] = 'synonyms.entity_autocomplete';
$element['#autocomplete_route_parameters'] = [
'target_type' => $element['#target_type'],
'token' => $token,
];
return $element;
}
/**
* Form element validation handler for synonyms_entity_autocomplete elements.
*/
public static function validateEntityAutocomplete(array &$element, FormStateInterface $form_state, array &$complete_form) {
$tokens = Tags::explode($form_state->getValue($element['#parents']));
$value = [];
$autocomplete_service = \Drupal::getContainer()->get('synonyms.behavior.autocomplete');
foreach ($tokens as $token) {
$entity_id = self::extractEntityIdFromAutocompleteInput($token);
if (!$entity_id) {
$lookup = $autocomplete_service->autocompleteLookup($token, $element['#autocomplete_route_parameters']['token']);
$lookup = array_shift($lookup);
if ($lookup) {
$entity_id = $lookup['entity_id'];
}
}
if ($entity_id) {
$value[] = ['target_id' => $entity_id];
}
}
$form_state->setValueForElement($element, $value);
}
/**
* Extracts the entity ID from the autocompletion result.
*
* @param string $input
* The input coming from the autocompletion result.
*
* @return mixed|null
*/
public static function extractEntityIdFromAutocompleteInput($input) {
$match = NULL;
// Take "label (entity id)', match the ID from parenthesis when it's a
// number.
if (preg_match("/.+\s\((\d+)\)/", $input, $matches)) {
$match = $matches[1];
}
return $match;
}
}
@@ -0,0 +1,161 @@
<?php
namespace Drupal\synonyms\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Select;
/**
* Form element for synonyms-friendly entity select.
*
* @FormElement("synonyms_entity_select")
*/
class SynonymsEntitySelect extends Select {
/**
* Delimiter to use when separating entity ID and its synonym.
*
* @var string
*/
const DELIMITER = ':';
/**
* {@inheritdoc}
*/
public function getInfo() {
$info = parent::getInfo();
// Target entity type.
$info['#target_type'] = NULL;
// Which SelectionInterface plugin to use for retrieving referenceable
// entities.
$info['#handler'] = 'default';
// Array of settings for the selection handler.
$info['#handler_settings'] = [];
// Put actual value under this key in the associative array.
$info['#key_column'] = 'target_id';
array_unshift($info['#process'], [get_class($this), 'elementSynonymsEntitySelect']);
$info['#element_validate'][] = [get_class($this), 'validateEntitySelect'];
return $info;
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
$return = parent::valueCallback($element, $input, $form_state);
if (is_null($return) && isset($element['#default_value'])) {
$return = $element['#default_value'];
}
// Force default value (entity ID(-s)) to be strings. Otherwise we are
// hitting the situation when all synonyms are highlighted as selected.
// This code snippet explains the problem:
// $a = [25];
// $k = '25:25';
// in_array($k, $a); // Yields TRUE, because PHP seems to compare int to int
// and not string-wise.
if (is_array($return)) {
$return = array_map(function($item) {
return (string) $item;
}, $return);
}
elseif (!is_null($return)) {
$return = (string) $return;
}
return $return;
}
/**
* Form element process callback for 'synonyms_entity_select' type.
*/
public static function elementSynonymsEntitySelect(array &$element, FormStateInterface $form_state, array &$complete_form) {
$options = [];
$selection = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance([
'target_type' => $element['#target_type'],
'handler' => $element['#handler'],
'handler_settings' => $element['#handler_settings'],
'entity' => NULL,
]);
$bundle_info = \Drupal::getContainer()->get('entity_type.bundle.info')->getBundleInfo($element['#target_type']);
$referenceable_entities = $selection->getReferenceableEntities();
$entities = [];
foreach ($referenceable_entities as $bundle_entity_ids) {
$entities = array_merge($entities, array_keys($bundle_entity_ids));
}
if (!empty($entities)) {
$entities = \Drupal::entityTypeManager()->getStorage($element['#target_type'])->loadMultiple($entities);
}
foreach ($referenceable_entities as $bundle => $entity_ids) {
$key = (string) $bundle_info[$bundle]['label'];
$options[$key] = [];
$synonyms = \Drupal::getContainer()->get('synonyms.behavior.select')->getSynonymsMultiple(array_intersect_key($entities, $entity_ids));
foreach ($entity_ids as $entity_id => $label) {
$options[$key][$entity_id] = $label;
foreach ($synonyms[$entity_id] as $synonym) {
$options[$key][$entity_id . self::DELIMITER . $synonym['synonym']] = $synonym['wording'];
}
}
asort($options[$key]);
}
if (count($options) == 1) {
// Strip away the bundle optgroup if it's the only bundle.
$options = reset($options);
}
$element['#options'] = $options;
return $element;
}
/**
* Form element validation handler for synonyms_entity_select elements.
*/
public static function validateEntitySelect(array &$element, FormStateInterface $form_state, array &$complete_form) {
$value = $form_state->getValue($element['#parents']);
if (!isset($element['#multiple']) || !$element['#multiple']) {
$value = [$value];
}
$unique = [];
foreach ($value as $v) {
if ($v !== '') {
if (!is_numeric($v)) {
$v = explode(self::DELIMITER, $v, 2)[0];
}
$unique[$v] = $v;
}
}
$items = [];
foreach ($unique as $v) {
$items[] = [
$element['#key_column'] => $v,
];
}
if (!isset($element['#multiple']) || !$element['#multiple']) {
$items = reset($items);
}
$form_state->setValueForElement($element, $items);
}
}
@@ -0,0 +1,235 @@
<?php
namespace Drupal\synonyms\Entity;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\synonyms\SynonymInterface;
use Drupal\synonyms\SynonymProviderPluginCollection;
use Drupal\synonyms\SynonymsProviderInterface;
/**
* Synonym configuration entity.
*
* @ConfigEntityType(
* id = "synonym",
* label = @Translation("Synonym configuration"),
* handlers = {
* "list_builder" = "Drupal\Core\Config\Entity\ConfigEntityListBuilder",
* "form" = {
* "add" = "Drupal\synonyms\Form\SynonymForm",
* "edit" = "Drupal\synonyms\Form\SynonymForm",
* "delete" = "Drupal\synonyms\Form\SynonymDeleteForm"
* }
* },
* config_prefix = "synonym",
* admin_permission = "administer synonyms",
* entity_keys = {
* "id" = "id"
* },
* links = {
* "edit-form" = "/admin/structure/synonyms/{synonym}",
* "delete-form" = "/admin/structure/synonyms/{synonym}/delete"
* }
* )
*/
class Synonym extends ConfigEntityBase implements SynonymInterface {
/**
* Plugin ID that corresponds to this config entry.
*
* @var string
*/
protected $provider_plugin;
/**
* Base plugin ID that corresponds to this config entry.
*
* @var string
*/
protected $base_provider_plugin;
/**
* Plugin configuration.
*
* @var array
*/
protected $provider_configuration = [];
/**
* Controlled behavior service.
*
* @var string
*/
protected $behavior;
/**
* Behavior configuration.
*
* @var array
*/
protected $behavior_configuration = [];
/**
* The plugin collection that stores synonym provider plugins.
*
* @var \Drupal\synonyms\SynonymProviderPluginCollection
*/
protected $pluginCollection;
/**
* {@inheritdoc}
*/
public function label() {
return $this->getProviderPluginInstance()->getPluginDefinition()['label'];
}
/**
* {@inheritdoc}
*/
public function getProviderPluginInstance() {
return $this->getPluginCollection()->get($this->getProviderPlugin());
}
/**
* {@inheritdoc}
*/
public function getProviderPlugin() {
return $this->provider_plugin;
}
/**
* {@inheritdoc}
*/
public function setProviderPlugin($plugin) {
$this->provider_plugin = $plugin;
}
/**
* {@inheritdoc}
*/
public function getProviderConfiguration() {
if ($this->getProviderPluginInstance() instanceof ConfigurablePluginInterface) {
return $this->getPluginCollection()->get($this->getProviderPlugin())->getConfiguration();
}
return $this->provider_configuration;
}
/**
* {@inheritdoc}
*/
public function setProviderConfiguration(array $provider_configuration) {
if ($this->getProviderPluginInstance() instanceof ConfigurablePluginInterface) {
$this->getProviderPluginInstance()->setConfiguration($provider_configuration);
}
$this->provider_configuration = $provider_configuration;
}
/**
* {@inheritdoc}
*/
public function getBehaviorConfiguration() {
return $this->behavior_configuration;
}
/**
* {@inheritdoc}
*/
public function setBehaviorConfiguration(array $behavior_configuration) {
$this->behavior_configuration = $behavior_configuration;
}
/**
* Gets the plugin collections used by this entity.
*
* @return SynonymProviderPluginCollection[]
* An array of plugin collections, keyed by the property name they use to
* store their configuration.
*/
public function getPluginCollections() {
return ['provider_configuration' => $this->getPluginCollection()];
}
/**
* {@inheritdoc}
*/
public static function postLoad(EntityStorageInterface $storage, array &$entities) {
parent::postLoad($storage, $entities);
foreach ($entities as $entity) {
$entity->addCacheTags([self::cacheTagConstruct($entity->get('behavior'), $entity->getProviderPluginInstance()->getPluginDefinition()['controlled_entity_type'], $entity->getProviderPluginInstance()->getPluginDefinition()['controlled_bundle'])]);
}
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
// Update the "static" properties. We keep them only to be able to leverage
// Schema API through them.
$this->base_provider_plugin = $this->getProviderPluginInstance()->getBaseId();
$this->behavior = $this->getProviderPluginInstance()->getBehaviorService();
// Make sure we have appropriate cache tags in this entity. If it was just
// created and runs its first save it might not have it set up yet.
$this->addCacheTags([self::cacheTagConstruct($this->behavior, $this->getProviderPluginInstance()->getPluginDefinition()['controlled_entity_type'], $this->getProviderPluginInstance()->getPluginDefinition()['controlled_bundle'])]);
}
/**
* Construct a cache tag.
*
* Construct a cache tag that represents synonyms config for a given behavior,
* entity type, and bundle.
*
* @param string $behavior
* Synonyms behavior whose cache tag is requested
* @param string $entity_type
* Entity type whose cache tag is requested
* @param string $bundle
* Bundle whose cache tag is requested
*
* @return string
* Cache tag
*/
public static function cacheTagConstruct($behavior, $entity_type, $bundle) {
return 'synonyms:' . $behavior . '.' . $entity_type . '.' . $bundle;
}
/**
* Encapsulates the creation of entity's LazyPluginCollection.
*
* @return SynonymProviderPluginCollection
* The entity's plugin collection.
*/
protected function getPluginCollection() {
if (!$this->pluginCollection && $this->getProviderPlugin()) {
$this->pluginCollection = new SynonymProviderPluginCollection(\Drupal::service('plugin.manager.synonyms_provider'), $this->getProviderPlugin(), $this->provider_configuration);
}
return $this->pluginCollection;
}
/**
* {@inheritdoc}
*/
protected function invalidateTagsOnSave($update) {
parent::invalidateTagsOnSave($update);
Cache::invalidateTags($this->cacheTags);
}
/**
* {@inheritdoc}
*/
protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
$cacheability_metadata = new CacheableMetadata();
foreach ($entities as $entity) {
$cacheability_metadata->addCacheableDependency($entity);
}
Cache::invalidateTags($cacheability_metadata->getCacheTags());
}
}
@@ -0,0 +1,45 @@
<?php
namespace Drupal\synonyms\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Delete form for synonym config entity.
*/
class SynonymDeleteForm extends EntityConfirmFormBase {
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete synonym configuration %name?', ['%name' => $this->entity->label()]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.synonym.overview');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete');
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->delete();
drupal_set_message($this->t('Synonym configuration %label has been deleted.', ['%label' => $this->entity->label()]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
}
@@ -0,0 +1,292 @@
<?php
namespace Drupal\synonyms\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\synonyms\SynonymInterface;
use Drupal\synonyms\SynonymsProviderPluginManager;
use Drupal\synonyms\SynonymsService\Behavior\SynonymsBehaviorConfigurableInterface;
use Drupal\synonyms\SynonymsService\BehaviorService;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Entity form for 'synonym' config entity type.
*/
class SynonymForm extends EntityForm {
/**
* @var SynonymInterface
*/
protected $entity;
/**
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $entityQuery;
/**
* Entity type manager.
*
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Entity type bundle info.
*
* @var EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* @var SynonymsProviderPluginManager
*/
protected $synonymsProviderPluginManager;
/**
* @var BehaviorService
*/
protected $behaviorServices;
/**
* Entity type that is being edited/added.
*
* @var EntityTypeInterface
*/
protected $controlledEntityType;
/**
* Bundle that is being edited/added.
*
* @var string
*/
protected $controlledBundle;
/**
* Service ID that is being edited/added.
*
* @var string
*/
protected $behaviorServiceId;
/**
* @var ContainerInterface
*/
protected $container;
public function __construct(QueryFactory $entity_query, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, SynonymsProviderPluginManager $synonyms_provider_plugin_manager, BehaviorService $behavior_services, ContainerInterface $container) {
$this->entityQuery = $entity_query;
$this->entityTypeManager = $entity_type_manager;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->synonymsProviderPluginManager = $synonyms_provider_plugin_manager;
$this->behaviorServices = $behavior_services;
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.query'),
$container->get('entity_type.manager'),
$container->get('entity_type.bundle.info'),
$container->get('plugin.manager.synonyms_provider'),
$container->get('synonyms.behaviors'),
$container
);
}
/**
* {@inheritdoc}
*/
protected function init(FormStateInterface $form_state) {
parent::init($form_state);
if ($this->entity->isNew()) {
$this->controlledEntityType = $this->getRequest()->get('synonyms_entity_type')->id();
$this->controlledBundle = $this->getRequest()->get('bundle');
$this->behaviorServiceId = $this->getRouteMatch()->getRawParameter('synonyms_behavior_service');
}
else {
$plugin_definition = $this->entity->getProviderPluginInstance()->getPluginDefinition();
$this->controlledEntityType = $plugin_definition['controlled_entity_type'];
$this->controlledBundle = $plugin_definition['controlled_bundle'];
$this->behaviorServiceId = $plugin_definition['synonyms_behavior_service'];
}
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$class = get_class($this);
$provider_plugin = $this->entity->getProviderPlugin();
if ($form_state->getValue('provider_plugin')) {
$provider_plugin = $form_state->getValue('provider_plugin');
}
$form['id'] = [
'#type' => 'value',
'#value' => str_replace(':', '.', $provider_plugin),
];
$options = [];
foreach ($this->synonymsProviderPluginManager->getDefinitions() as $plugin_id => $plugin) {
if ($plugin['controlled_entity_type'] == $this->controlledEntityType && $plugin['controlled_bundle'] == $this->controlledBundle && $plugin['synonyms_behavior_service'] == $this->behaviorServiceId) {
$options[$plugin_id] = $plugin['label'];
}
}
$form['provider_plugin'] = [
'#type' => 'select',
'#title' => $this->t('Synonyms provider'),
'#description' => $this->t('Select what synonyms provider it should represent.'),
'#required' => TRUE,
'#options' => $options,
'#default_value' => $this->entity->getProviderPlugin(),
'#ajax' => [
'wrapper' => 'synonyms-entity-configuration-ajax-wrapper',
'event' => 'change',
'callback' => [$class, 'ajaxForm'],
],
];
$form['ajax_wrapper'] = [
'#prefix' => '<div id="synonyms-entity-configuration-ajax-wrapper">',
'#suffix' => '</div>',
];
$form['ajax_wrapper']['provider_configuration'] = [
'#tree' => TRUE,
'#title' => $this->t('Provider settings'),
'#open' => TRUE,
];
$form['ajax_wrapper']['behavior_configuration'] = [
'#tree' => TRUE,
'#title' => $this->t('Behavior settings'),
'#open' => TRUE,
];
if ($provider_plugin) {
$provider_plugin_instance = $this->entity->getProviderPluginInstance();
if ($provider_plugin_instance instanceof PluginFormInterface) {
$form['ajax_wrapper']['provider_configuration']['#type'] = 'details';
$form['ajax_wrapper']['provider_configuration'] += $provider_plugin_instance->buildConfigurationForm($form['ajax_wrapper']['provider_configuration'], $form_state);
}
$behavior_service_instance = $provider_plugin_instance->getBehaviorServiceInstance();
if ($behavior_service_instance instanceof SynonymsBehaviorConfigurableInterface) {
$form['ajax_wrapper']['behavior_configuration']['#type'] = 'details';
$form['ajax_wrapper']['behavior_configuration'] += $behavior_service_instance->buildConfigurationForm($form['ajax_wrapper']['behavior_configuration'], $form_state, $this->entity->getBehaviorConfiguration(), $this->entity);
}
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
if ($this->entity->getProviderPluginInstance() instanceof PluginFormInterface) {
$this->entity->getProviderPluginInstance()->validateConfigurationForm($form['ajax_wrapper']['provider_configuration'], $this->getSubFormState('provider_configuration', $form, $form_state));
}
if ($this->entity->getProviderPluginInstance()->getBehaviorServiceInstance() instanceof SynonymsBehaviorConfigurableInterface) {
$this->entity->getProviderPluginInstance()->getBehaviorServiceInstance()->validateConfigurationForm($form['ajax_wrapper']['behavior_configuration'], $this->getSubFormState('behavior_configuration', $form, $form_state), $this->entity);
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
if ($this->entity->getProviderPluginInstance() instanceof PluginFormInterface) {
$this->entity->getProviderPluginInstance()->submitConfigurationForm($form['ajax_wrapper']['provider_configuration'], $this->getSubFormState('provider_configuration', $form, $form_state));
}
if ($this->entity->getProviderPluginInstance()->getBehaviorServiceInstance() instanceof SynonymsBehaviorConfigurableInterface) {
$this->entity->setBehaviorConfiguration($this->entity->getProviderPluginInstance()->getBehaviorServiceInstance()->submitConfigurationForm($form['ajax_wrapper']['behavior_configuration'], $this->getSubFormState('behavior_configuration', $form, $form_state), $this->entity));
}
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$status = $this->entity->save();
if ($status) {
drupal_set_message($this->t('Saved the %label synonym configuration.', [
'%label' => $this->entity->label(),
]));
}
else {
drupal_set_message($this->t('The %label synonym configuration was not saved.', [
'%label' => $this->entity->label(),
]), 'error');
}
$form_state->setRedirect('entity.synonym.overview');
}
/**
* Check whether entity with such ID already exists.
*
* @param string $id
* Entity ID to check
*
* @return bool
* Whether entity with such ID already exists.
*/
public function exist($id) {
$entity = $this->entityQuery->get('synonym')
->condition('id', $id)
->execute();
return (bool) $entity;
}
/**
* Ajax callback.
*/
public static function ajaxForm(array &$form, FormStateInterface $form_state, Request $request) {
return $form['ajax_wrapper'];
}
/**
* Supportive method to create sub-form-states.
*
* @param string $element_name
* Name of the nested form element for which to create a sub form state
* @param array $form
* Full form array
* @param FormStateInterface $form_state
* Full form state out of which to create sub form state
*
* @return SubformState
* Sub form state object generated based on the input arguments
*/
protected function getSubFormState($element_name, array $form, FormStateInterface $form_state) {
return SubformState::createForSubform($form['ajax_wrapper'][$element_name], $form, $form_state);
}
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\synonyms\ParamConverter;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Symfony\Component\Routing\Route;
/**
* Param converter service for entity types.
*/
class EntityTypeParamConverter implements ParamConverterInterface {
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* EntityTypeParamConverter constructor.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
return $this->entityTypeManager->getDefinition($value);
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, Route $route) {
return (!empty($definition['type']) && $definition['type'] == 'synonyms_entity_type');
}
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\synonyms\ParamConverter;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Drupal\synonyms\SynonymsService\BehaviorService;
use Symfony\Component\Routing\Route;
/**
* Param converter for synonyms behavior service.
*/
class SynonymsBehaviorServiceParamConverter implements ParamConverterInterface {
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* SynonymsBehaviorServiceParamConverter constructor.
*/
public function __construct(BehaviorService $behavior_service) {
$this->behaviorService = $behavior_service;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
return $this->behaviorService->getBehaviorService($value)['service'];
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, Route $route) {
return (!empty($definition['type']) && $definition['type'] == 'synonyms_behavior_service');
}
}
@@ -0,0 +1,113 @@
<?php
namespace Drupal\synonyms\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\synonyms\SynonymsService\BehaviorService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Derivative for entity_reference synonyms provider plugin.
*/
class EntityReferenceField extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* Entity type manager.
*
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* Entity type bundle info.
*
* @var EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* @var EntityFieldManagerInterface
*/
protected $entityFieldManager;
function __construct(EntityTypeManagerInterface $entity_type_manager, BehaviorService $behavior_service, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityFieldManagerInterface $entity_field_manager) {
$this->entityTypeManager = $entity_type_manager;
$this->behaviorService = $behavior_service;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->entityFieldManager = $entity_field_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager'),
$container->get('synonyms.behaviors'),
$container->get('entity_type.bundle.info'),
$container->get('entity_field.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$implemented_interfaces = new \ReflectionClass($base_plugin_definition['class']);
$implemented_interfaces = $implemented_interfaces->getInterfaceNames();
foreach ($this->behaviorService->getBehaviorServices() as $service_id => $behavior) {
$required_interfaces = $behavior['required_interfaces'];
$diff = array_diff($required_interfaces, $implemented_interfaces);
if (empty($diff)) {
// This plugin has implemented all required interfaces for this
// behavior.
foreach ($this->entityTypeManager->getDefinitions() as $entity_type) {
if ($entity_type instanceof ContentEntityType) {
foreach ($this->entityTypeBundleInfo->getBundleInfo($entity_type->id()) as $bundle => $bundle_info) {
$fields = $this->entityFieldManager->getFieldDefinitions($entity_type->id(), $bundle);
foreach ($fields as $field) {
if ($field->getType() == 'entity_reference') {
$derivative_name = implode('_', [
$service_id,
$entity_type->id(),
$bundle,
$field->getName(),
]);
$this->derivatives[$derivative_name] = $base_plugin_definition;
$this->derivatives[$derivative_name]['label'] = $this->t('@behavior on @field', [
'@behavior' => $behavior['service']->getTitle(),
'@field' => $field->getLabel(),
]);
$this->derivatives[$derivative_name]['synonyms_behavior_service'] = $service_id;
$this->derivatives[$derivative_name]['controlled_entity_type'] = $entity_type->id();
$this->derivatives[$derivative_name]['controlled_bundle'] = $bundle;
$this->derivatives[$derivative_name]['field'] = $field->getName();
}
}
}
}
}
}
}
return $this->derivatives;
}
}
@@ -0,0 +1,133 @@
<?php
namespace Drupal\synonyms\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\synonyms\SynonymsService\BehaviorService;
use Drupal\synonyms\SynonymsService\FieldTypeToSynonyms;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Derivative for synonyms provider plugins.
*/
class Field extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* Entity type manager.
*
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* Entity type bundle info.
*
* @var EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* @var EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* @var FieldTypeToSynonyms
*/
protected $fieldTypeToSynonyms;
function __construct(EntityTypeManagerInterface $entity_type_manager, BehaviorService $behavior_service, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityFieldManagerInterface $entity_field_manager, FieldTypeToSynonyms $field_type_to_synonyms) {
$this->entityTypeManager = $entity_type_manager;
$this->behaviorService = $behavior_service;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->entityFieldManager = $entity_field_manager;
$this->fieldTypeToSynonyms = $field_type_to_synonyms;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager'),
$container->get('synonyms.behaviors'),
$container->get('entity_type.bundle.info'),
$container->get('entity_field.manager'),
$container->get('synonyms.provider.field_type_to_synonyms')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$implemented_interfaces = new \ReflectionClass($base_plugin_definition['class']);
$implemented_interfaces = $implemented_interfaces->getInterfaceNames();
$field_type_to_property_map = $this->fieldTypeToSynonyms->getSimpleFieldTypeToPropertyMap();
foreach ($this->behaviorService->getBehaviorServices() as $service_id => $behavior) {
$required_interfaces = $behavior['required_interfaces'];
$diff = array_diff($required_interfaces, $implemented_interfaces);
if (empty($diff)) {
// This plugin has implemented all required interfaces for this
// behavior.
foreach ($this->entityTypeManager->getDefinitions() as $entity_type) {
if ($entity_type instanceof ContentEntityType) {
foreach ($this->entityTypeBundleInfo->getBundleInfo($entity_type->id()) as $bundle => $bundle_info) {
$base_fields = $this->entityFieldManager->getBaseFieldDefinitions($entity_type->id());
$fields = $this->entityFieldManager->getFieldDefinitions($entity_type->id(), $bundle);
switch ($base_plugin_definition['id']) {
case 'base_field':
$fields = $base_fields;
break;
case 'field':
$fields = array_diff_key($fields, $base_fields);
break;
}
foreach ($fields as $field) {
if ($field->getName() != 'synonyms' && isset($field_type_to_property_map[$field->getType()])) {
$derivative_name = implode('_', [
$service_id,
$entity_type->id(),
$bundle,
$field->getName(),
]);
$this->derivatives[$derivative_name] = $base_plugin_definition;
$this->derivatives[$derivative_name]['label'] = $this->t('@behavior on @field', [
'@behavior' => $behavior['service']->getTitle(),
'@field' => $field->getLabel(),
]);
$this->derivatives[$derivative_name]['synonyms_behavior_service'] = $service_id;
$this->derivatives[$derivative_name]['controlled_entity_type'] = $entity_type->id();
$this->derivatives[$derivative_name]['controlled_bundle'] = $bundle;
$this->derivatives[$derivative_name]['field'] = $field->getName();
}
}
}
}
}
}
}
return $this->derivatives;
}
}
@@ -0,0 +1,78 @@
<?php
namespace Drupal\synonyms\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides synonyms argument validator plugin definitions for all entity types.
*/
class ViewsSynonymsEntityArgumentValidator extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The base plugin ID this derivative is for.
*
* @var string
*/
protected $basePluginId;
/**
* The entity type manager.
*
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs an ViewsSynonymsEntityArgumentValidator object.
*/
public function __construct($base_plugin_id, EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
$this->basePluginId = $base_plugin_id;
$this->entityTypeManager = $entity_type_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('entity_type.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$entity_types = $this->entityTypeManager->getDefinitions();
$this->derivatives = [];
foreach ($entity_types as $entity_type_id => $entity_type) {
if ($entity_type instanceof ContentEntityTypeInterface) {
$this->derivatives[$entity_type_id] = [
'id' => 'synonyms_entity:' . $entity_type_id,
'provider' => 'synonyms',
'title' => $this->t('Synonyms of @entity_type', [
'@entity_type' => $entity_type->getLowercaseLabel(),
]),
'help' => $this->t('Validate @label', ['@label' => $entity_type->getLabel()]),
'entity_type' => $entity_type_id,
'class' => $base_plugin_definition['class'],
];
}
}
return $this->derivatives;
}
}
@@ -0,0 +1,157 @@
<?php
namespace Drupal\synonyms\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\synonyms\SynonymsService\BehaviorService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'synonyms friendly autocomplete' widget.
*
* @FieldWidget(
* id = "synonyms_autocomplete",
* label = @Translation("Synonyms-friendly autocomplete"),
* description = @Translation("An autocomplete with entities and their synonyms."),
* field_types = {
* "entity_reference"
* },
* multiple_values = TRUE
* )
*/
class EntityReferenceSynonymsAutocomplete extends WidgetBase implements ContainerFactoryPluginInterface {
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* {@inheritdoc}
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, BehaviorService $behavior_service) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
$this->behaviorService = $behavior_service;
}
/**
* {@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['third_party_settings'],
$container->get('synonyms.behaviors')
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'suggestion_size' => 10,
'suggest_only_unique' => FALSE,
'match' => 'CONTAINS',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['suggestion_size'] = [
'#type' => 'textfield',
'#title' => $this->t('Suggestions Size'),
'#description' => $this->t('Please, enter how many suggested entities to show in the autocomplete textfield.'),
'#required' => TRUE,
'#default_value' => $this->getSetting('suggestion_size'),
];
$elements['suggest_only_unique'] = [
'#type' => 'checkbox',
'#title' => t('Suggest only one entry per entity'),
'#description' => t('If you want to include only name or a single synonym, suggesting a particular entity, while disregarding all ongoing ones, please, tick this checkbox on.'),
'#default_value' => $this->getSetting('suggest_only_unique'),
];
$elements['match'] = [
'#type' => 'radios',
'#title' => $this->t('Match operator'),
'#description' => $this->t('Choose how to match the keyword against existing data.'),
'#options' => $this->getMatchOperatorOptions(),
'#default_value' => $this->getSetting('match'),
'#required' => TRUE,
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$summary[] = $this->t('Suggestion size: @size', [
'@size' => $this->getSetting('suggestion_size'),
]);
$summary[] = $this->t('Only unique: @unique', [
'@unique' => $this->getSetting('suggest_only_unique') ? $this->t('Yes') : $this->t('No'),
]);
$summary[] = $this->t('Match: @match', [
'@match' => $this->getMatchOperatorOptions()[$this->getSetting('match')],
]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$default_value = [];
foreach ($items as $item) {
if ($item->entity) {
$default_value[] = $item->entity;
}
}
$element += [
'#type' => 'synonyms_entity_autocomplete',
'#target_type' => $this->getFieldSetting('target_type'),
'#target_bundles' => $this->getFieldSetting('handler_settings')['target_bundles'],
'#suggestion_size' => $this->getSetting('suggestion_size'),
'#suggest_only_unique' => $this->getSetting('suggest_only_unique'),
'#match' => $this->getSetting('match'),
'#default_value' => $default_value,
];
return $element;
}
/**
* Returns the options for the match operator.
*
* @return array
* List of options.
*/
protected function getMatchOperatorOptions() {
return [
'STARTS_WITH' => t('Starts with'),
'CONTAINS' => t('Contains'),
];
}
}
@@ -0,0 +1,56 @@
<?php
namespace Drupal\synonyms\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'synonyms friendly select' widget.
*
* @FieldWidget(
* id = "synonyms_select",
* label = @Translation("Synonyms-friendly select"),
* description = @Translation("A dropdown with entities and their synonyms."),
* field_types = {
* "entity_reference"
* },
* multiple_values = TRUE
* )
*/
class EntityReferenceSynonymsSelect extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$default_value = [];
foreach ($items as $item) {
if ($item->{$this->getKeyColumn()}) {
$default_value[] = $item->{$this->getKeyColumn()};
}
}
$element += [
'#type' => 'synonyms_entity_select',
'#key_column' => $this->getKeyColumn(),
'#target_type' => $this->getFieldSetting('target_type'),
'#handler' => $this->fieldDefinition->getSetting('handler'),
'#handler_settings' => $this->fieldDefinition->getSetting('handler_settings') ?: [],
'#multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
'#default_value' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple() ? $default_value : reset($default_value),
];
return $element;
}
/**
* Get name of the column that is managed by this widget in the field
* .
* @return string
*/
protected function getKeyColumn() {
return $this->fieldDefinition->getFieldStorageDefinition()->getPropertyNames()[0];
}
}
@@ -0,0 +1,58 @@
<?php
namespace Drupal\synonyms\Plugin\Synonyms\Provider;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Good starting point for a synonyms provider plugin.
*/
abstract class AbstractProvider extends PluginBase implements SynonymsProviderInterface, ContainerFactoryPluginInterface {
use StringTranslationTrait;
/**
* @var ContainerInterface
*/
protected $container;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ContainerInterface $container) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container
);
}
/**
* {@inheritdoc}
*/
public function getBehaviorService() {
return $this->getPluginDefinition()['synonyms_behavior_service'];
}
/**
* {@inheritdoc}
*/
public function getBehaviorServiceInstance() {
return $this->container->get($this->getPluginDefinition()['synonyms_behavior_service']);
}
}
@@ -0,0 +1,128 @@
<?php
namespace Drupal\synonyms\Plugin\Synonyms\Provider;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\ConditionInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindTrait;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFormatWordingProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFormatWordingTrait;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetTrait;
use Drupal\synonyms\SynonymsService\FieldTypeToSynonyms;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provide synonyms from base fields.
*
* @SynonymsProvider(
* id = "base_field",
* deriver = "Drupal\synonyms\Plugin\Derivative\Field"
* )
*/
class BaseField extends AbstractProvider implements SynonymsGetProviderInterface, SynonymsFindProviderInterface, SynonymsFormatWordingProviderInterface {
use SynonymsGetTrait, SynonymsFindTrait, SynonymsFormatWordingTrait;
/**
* @var EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* @var FieldTypeToSynonyms
*/
protected $fieldTypeToSynonyms;
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var Connection
*/
protected $database;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FieldTypeToSynonyms $field_type_to_synonyms, EntityTypeManagerInterface $entity_type_manager, Connection $database, ContainerInterface $container) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $container);
$this->entityFieldManager = $entity_field_manager;
$this->fieldTypeToSynonyms = $field_type_to_synonyms;
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_field.manager'),
$container->get('synonyms.provider.field_type_to_synonyms'),
$container->get('entity_type.manager'),
$container->get('database'),
$container
);
}
/**
* {@inheritdoc}
*/
public function getSynonyms(ContentEntityInterface $entity, array $behavior_configuration = []) {
$map = $this->fieldTypeToSynonyms->getSimpleFieldTypeToPropertyMap();
$field_type = $entity->getFieldDefinition($this->getPluginDefinition()['field'])->getType();
$synonyms = [];
if (isset($map[$field_type])) {
foreach ($entity->get($this->getPluginDefinition()['field']) as $item) {
$synonyms[] = $item->{$map[$field_type]};
}
}
return $synonyms;
}
/**
* {@inheritdoc}
*/
public function synonymsFind(ConditionInterface $condition) {
$entity_type_definition = $this->entityTypeManager->getDefinition($this->getPluginDefinition()['controlled_entity_type']);
$entity_keys = $entity_type_definition->getKeys();
$query = $this->database->select($entity_type_definition->getDataTable(), 'base');
$query->addField('base', $entity_keys['id'], 'entity_id');
$query->addField('base', $this->getPluginDefinition()['field'], 'synonym');
if ($this->getPluginDefinition()['controlled_entity_type'] != $this->getPluginDefinition()['controlled_bundle'] && isset($entity_keys['bundle']) && $entity_keys['bundle']) {
$query->condition($entity_keys['bundle'], $this->getPluginDefinition()['controlled_bundle']);
}
$this->synonymsFindProcessCondition($condition, $this->getPluginDefinition()['field'], $entity_keys['id']);
$query->condition($condition);
return $query->execute();
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$field = $this->entityFieldManager->getFieldDefinitions($this->getPluginDefinition()['controlled_entity_type'], $this->getPluginDefinition()['controlled_bundle'])[$this->getPluginDefinition()['field']];
return [
$field->getConfigDependencyKey() => [$field->getConfigDependencyName()],
];
}
}
@@ -0,0 +1,191 @@
<?php
namespace Drupal\synonyms\Plugin\Synonyms\Provider;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\ConditionInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryBase;
use Drupal\Core\Entity\Query\Sql\Condition;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindTrait;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFormatWordingProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFormatWordingTrait;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provide synonyms from entity reference field type.
*
* @SynonymsProvider(
* id = "entityreference_field",
* deriver = "Drupal\synonyms\Plugin\Derivative\EntityReferenceField"
* )
*/
class EntityReferenceField extends AbstractProvider implements SynonymsGetProviderInterface, SynonymsFindProviderInterface, SynonymsFormatWordingProviderInterface {
use SynonymsGetTrait, SynonymsFindTrait, SynonymsFormatWordingTrait;
/**
* @var EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var Connection
*/
protected $database;
/**
* @var QueryFactory
*/
protected $entityQueryFactory;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, Connection $database, QueryFactory $entity_query_factory, ContainerInterface $container) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $container);
$this->entityFieldManager = $entity_field_manager;
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
$this->entityQueryFactory = $entity_query_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_field.manager'),
$container->get('entity_type.manager'),
$container->get('database'),
$container->get('entity.query'),
$container
);
}
/**
* {@inheritdoc}
*/
public function getSynonyms(ContentEntityInterface $entity, array $behavior_configuration = []) {
$synonyms = [];
foreach ($entity->get($this->getPluginDefinition()['field']) as $item) {
$synonyms[] = $item->entity->label();
}
return $synonyms;
}
/**
* {@inheritdoc}
*/
public function synonymsFind(ConditionInterface $condition) {
$entity_type_definition = $this->entityTypeManager->getDefinition($this->getPluginDefinition()['controlled_entity_type']);
$field = $this->entityFieldManager->getFieldDefinitions($this->getPluginDefinition()['controlled_entity_type'], $this->getPluginDefinition()['controlled_bundle']);
$field = $field[$this->getPluginDefinition()['field']];
$query = new FieldQuery($entity_type_definition, 'AND', $this->database, QueryBase::getNamespaces($this->entityQueryFactory->get($entity_type_definition->id(), 'AND')));
if ($entity_type_definition->hasKey('bundle')) {
$query->condition($entity_type_definition->getKey('bundle'), $this->getPluginDefinition()['controlled_bundle']);
}
$target_entity_type_definition = $this->entityTypeManager->getDefinition($field->getSetting('target_type'));
$label_column = $target_entity_type_definition->getKey('label');
// Some hacks.
if (!$label_column && $target_entity_type_definition->id() == 'user') {
$label_column = 'name';
}
if (!$label_column) {
// No label column is defined on target entity type, there's nothing we
// can do.
return [];
}
$synonym_column = $field->getName() . '.entity.' . $label_column;
$this->synonymsFindProcessCondition($condition, $synonym_column, $entity_type_definition->getKey('id'));
$conditions_array = $condition->conditions();
$entity_condition = new Condition($conditions_array['#conjunction'], $query);
// We will insert a dummy condition for a synonym column just to have the
// actual table.column data on the synonym column.
$hash = md5($synonym_column);
$query->condition($synonym_column, $hash);
unset($conditions_array['#conjunction']);
foreach ($conditions_array as $v) {
$entity_condition->condition($v['field'], $v['value'], $v['operator']);
}
$query->condition($entity_condition);
// We need run the entity query in order to force it into building a normal
// SQL query.
$query->execute();
// Now let's get "demapped" normal SQL query that will tell us explicitly
// where all entity properties/fields are stored among the SQL tables. Such
// data is what we need and we do not want to demap it manually.
$sql_query = $query->getSqlQuery();
// Swap the entity_id column into the alias "entity_id" as we are supposed
// to do in this method implementation.
$select_fields = &$sql_query->getFields();
$select_fields = ['entity_id' => $select_fields[$entity_type_definition->getKey('id')]];
$select_fields['entity_id']['alias'] = 'entity_id';
// We need some dummy extra condition to force them into re-compilation.
$sql_query->where('1 = 1');
$conditions_array = &$sql_query->conditions();
foreach ($conditions_array as $k => $condition_array) {
if (isset($condition_array['value']) && $condition_array['value'] == $hash) {
list($table_alias, $column) = explode('.', $condition_array['field']);
unset($conditions_array[$k]);
// Also unsetting the table aliased by this condition.
$tables = &$sql_query->getTables();
$real_table = $tables[$table_alias]['table'];
foreach ($tables as $k2 => $v2) {
if ($k2 != $table_alias && $real_table == $v2['table']) {
unset($tables[$table_alias]);
$table_alias = $k2;
}
}
$sql_query->addField($table_alias, $column, 'synonym');
break;
}
}
$result = $sql_query->execute();
return $result;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$field = $this->entityFieldManager->getFieldDefinitions($this->getPluginDefinition()['controlled_entity_type'], $this->getPluginDefinition()['controlled_bundle'])[$this->getPluginDefinition()['field']];
return [
$field->getConfigDependencyKey() => [$field->getConfigDependencyName()],
];
}
}
@@ -0,0 +1,216 @@
<?php
namespace Drupal\synonyms\Plugin\Synonyms\Provider;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\ConditionInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryBase;
use Drupal\Core\Entity\Query\Sql\Condition;
use Drupal\Core\Entity\Query\Sql\Query;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindTrait;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFormatWordingProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFormatWordingTrait;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetTrait;
use Drupal\synonyms\SynonymsService\FieldTypeToSynonyms;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provide synonyms from attached simple fields.
*
* @SynonymsProvider(
* id = "field",
* deriver = "Drupal\synonyms\Plugin\Derivative\Field"
* )
*/
class Field extends AbstractProvider implements SynonymsGetProviderInterface, SynonymsFindProviderInterface, SynonymsFormatWordingProviderInterface, DependentPluginInterface {
use SynonymsGetTrait, SynonymsFindTrait, SynonymsFormatWordingTrait;
/**
* @var EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* @var FieldTypeToSynonyms
*/
protected $fieldTypeToSynonyms;
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var Connection
*/
protected $database;
/**
* @var QueryFactory
*/
protected $entityQueryFactory;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FieldTypeToSynonyms $field_type_to_synonyms, EntityTypeManagerInterface $entity_type_manager, Connection $database, QueryFactory $entity_query_factory, ContainerInterface $container) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $container);
$this->entityFieldManager = $entity_field_manager;
$this->fieldTypeToSynonyms = $field_type_to_synonyms;
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
$this->entityQueryFactory = $entity_query_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_field.manager'),
$container->get('synonyms.provider.field_type_to_synonyms'),
$container->get('entity_type.manager'),
$container->get('database'),
$container->get('entity.query'),
$container
);
}
/**
* {@inheritdoc}
*/
public function getSynonyms(ContentEntityInterface $entity, array $behavior_configuration = []) {
$map = $this->fieldTypeToSynonyms->getSimpleFieldTypeToPropertyMap();
$field_type = $entity->getFieldDefinition($this->getPluginDefinition()['field'])->getType();
$synonyms = [];
if (isset($map[$field_type])) {
foreach ($entity->get($this->getPluginDefinition()['field']) as $item) {
$synonyms[] = $item->{$map[$field_type]};
}
}
return $synonyms;
}
/**
* {@inheritdoc}
*/
public function synonymsFind(ConditionInterface $condition) {
$entity_type_definition = $this->entityTypeManager->getDefinition($this->getPluginDefinition()['controlled_entity_type']);
$field = $this->entityFieldManager->getFieldDefinitions($this->getPluginDefinition()['controlled_entity_type'], $this->getPluginDefinition()['controlled_bundle']);
$field = $field[$this->getPluginDefinition()['field']];
$field_property = $this->fieldTypeToSynonyms->getSimpleFieldTypeToPropertyMap();
if (isset($field_property[$field->getType()])) {
$field_property = $field_property[$field->getType()];
}
else {
return [];
}
$query = new FieldQuery($entity_type_definition, 'AND', $this->database, QueryBase::getNamespaces($this->entityQueryFactory->get($entity_type_definition->id(), 'AND')));
if ($entity_type_definition->hasKey('bundle')) {
$query->condition($entity_type_definition->getKey('bundle'), $this->getPluginDefinition()['controlled_bundle']);
}
$synonym_column = $field->getName() . '.' . $field_property;
$this->synonymsFindProcessCondition($condition, $synonym_column, $entity_type_definition->getKey('id'));
$conditions_array = $condition->conditions();
$entity_condition = new Condition($conditions_array['#conjunction'], $query);
// We will insert a dummy condition for a synonym column just to have the
// actual table.column data on the synonym column.
$hash = md5($synonym_column);
$query->condition($synonym_column, $hash);
unset($conditions_array['#conjunction']);
foreach ($conditions_array as $v) {
$entity_condition->condition($v['field'], $v['value'], $v['operator']);
}
$query->condition($entity_condition);
// We need run the entity query in order to force it into building a normal
// SQL query.
$query->execute();
// Now let's get "demapped" normal SQL query that will tell us explicitly
// where all entity properties/fields are stored among the SQL tables. Such
// data is what we need and we do not want to demap it manually.
$sql_query = $query->getSqlQuery();
// Swap the entity_id column into the alias "entity_id" as we are supposed
// to do in this method implementation.
$select_fields = &$sql_query->getFields();
$select_fields = ['entity_id' => $select_fields[$entity_type_definition->getKey('id')]];
$select_fields['entity_id']['alias'] = 'entity_id';
// We need some dummy extra condition to force them into re-compilation.
$sql_query->where('1 = 1');
$conditions_array = &$sql_query->conditions();
foreach ($conditions_array as $k => $condition_array) {
if (isset($condition_array['value']) && $condition_array['value'] == $hash) {
list($table_alias, $column) = explode('.', $condition_array['field']);
unset($conditions_array[$k]);
// Also unsetting the table aliased by this condition.
$tables = &$sql_query->getTables();
$real_table = $tables[$table_alias]['table'];
foreach ($tables as $k2 => $v2) {
if ($k2 != $table_alias && $real_table == $v2['table']) {
unset($tables[$table_alias]);
$table_alias = $k2;
}
}
$sql_query->addField($table_alias, $column, 'synonym');
break;
}
}
$result = $sql_query->execute();
return $result;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$field = $this->entityFieldManager->getFieldDefinitions($this->getPluginDefinition()['controlled_entity_type'], $this->getPluginDefinition()['controlled_bundle'])[$this->getPluginDefinition()['field']];
return [
$field->getConfigDependencyKey() => [$field->getConfigDependencyName()],
];
}
}
/**
* Hacked implementation of Entity query.
*/
class FieldQuery extends Query {
/**
* We need to be able to extract SQL query object.
*
* @return \Drupal\Core\Database\Query\SelectInterface
*/
public function getSqlQuery() {
return $this->sqlQuery;
}
}
@@ -0,0 +1,33 @@
<?php
namespace Drupal\synonyms\Plugin;
use Drupal\Core\Field\FieldItemList;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetProviderInterface;
/**
* Field item list of "synonyms" computed base field.
*/
class SynonymsFieldItemList extends FieldItemList {
/**
* {@inheritdoc}
*/
public function getValue($include_computed = FALSE) {
$synonyms = [];
$entity = $this->getEntity();
$behavior_service = \Drupal::getContainer()->get('synonyms.behaviors');
$services = $behavior_service->getBehaviorServicesWithInterface(SynonymsGetProviderInterface::class);
foreach ($services as $service_id => $service) {
foreach ($behavior_service->getSynonymConfigEntities($service_id, $entity->getEntityTypeId(), $entity->bundle()) as $synonym_config) {
$synonyms = array_merge($synonyms, $synonym_config->getProviderPluginInstance()->getSynonyms($entity, $synonym_config->getBehaviorConfiguration()));
}
}
return array_unique($synonyms);
}
}
@@ -0,0 +1,144 @@
<?php
namespace Drupal\synonyms\Plugin\views\argument_validator;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Form\FormStateInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindProviderInterface;
use Drupal\synonyms\SynonymsService\BehaviorService;
use Drupal\views\Plugin\views\argument_validator\Entity;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Synonyms-friendly entity validator.
*
* @ViewsArgumentValidator(
* id = "synonyms_entity",
* deriver = "Drupal\synonyms\Plugin\Derivative\ViewsSynonymsEntityArgumentValidator"
* )
*/
class SynonymsEntity extends Entity {
protected $multipleCapable = FALSE;
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var QueryFactory
*/
protected $entityQuery;
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, EntityTypeManagerInterface $entity_type_manager, QueryFactory $entity_query, BehaviorService $behavior_service) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager);
$this->entityTypeManager = $entity_type_manager;
$this->entityQuery = $entity_query;
$this->behaviorService = $behavior_service;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_definition,
$plugin_definition,
$container->get('entity.manager'),
$container->get('entity_type.manager'),
$container->get('entity.query'),
$container->get('synonyms.behaviors')
);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['transform'] = [
'#type' => 'checkbox',
'#title' => $this->t('Transform dashes in URL to spaces.'),
'#default_value' => $this->options['transform'],
];
}
/**
* {@inheritdoc}
*/
public function validateArgument($argument) {
if ($this->options['transform']) {
$argument = str_replace('-', ' ', $argument);
}
$entity_type = $this->entityTypeManager->getDefinition($this->definition['entity_type']);
if ($entity_type->hasKey('label') || $entity_type->id() == 'user') {
$query = $this->entityQuery->get($entity_type->id());
// User entity type does not declare its label, while it does have one.
$label_column = $entity_type->id() == 'user' ? 'name' : $entity_type->getKey('label');
$query->condition($label_column, $argument, '=');
if ($entity_type->hasKey('bundle') && !empty($this->options['bundles'])) {
$query->condition($entity_type->getKey('bundle'), $this->options['bundles'], 'IN');
}
$result = $query->execute();
if (!empty($result)) {
$entities = $this->entityTypeManager->getStorage($entity_type->id())->loadMultiple($result);
foreach ($entities as $entity) {
if ($this->validateEntity($entity)) {
$this->argument->argument = $entity->id();
return TRUE;
}
}
}
}
// We've fallen through with search by entity name, now it's time to search
// by synonyms.
$condition = new Condition('AND');
$condition->condition(SynonymsFindProviderInterface::COLUMN_SYNONYM_PLACEHOLDER, $argument, '=');
foreach ($this->behaviorService->getBehaviorServicesWithInterface(SynonymsFindProviderInterface::class) as $service_id => $service) {
foreach ($this->behaviorService->getSynonymConfigEntities($service_id, $entity_type->id(), empty($this->options['bundles']) ? NULL : $this->options['bundles']) as $synonym_config) {
foreach ($synonym_config->getProviderPluginInstance()->synonymsFind(clone $condition) as $synonym) {
$entity = $this->entityTypeManager->getStorage($entity_type->id())->load($synonym->entity_id);
if ($this->validateEntity($entity)) {
$this->argument->argument = $entity->id();
return TRUE;
}
}
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['transform'] = ['default' => FALSE];
return $options;
}
}
@@ -0,0 +1,243 @@
<?php
namespace Drupal\synonyms\Plugin\views\filter;
use Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Filter entity by its name or one of its synonyms.
*
* @ViewsFilter("synonyms_entity")
*/
class SynonymsEntity extends FilterPluginBase {
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('entity_type.bundle.info')
);
}
/**
* {@inheritdoc}
*/
public function defineOptions() {
$options = parent::defineOptions();
$options['widget'] = [
'default' => 'autocomplete',
];
$options['target_bundles'] = [
'default' => NULL,
];
return $options;
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
switch ($this->options['widget']) {
case 'autocomplete':
$this->operator = 'IN';
break;
case 'select':
$this->operator = !$this->isExposed() || $this->options['expose']['multiple'] ? 'IN' : '=';
break;
}
}
/**
* {@inheritdoc}
*/
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildExtraOptionsForm($form, $form_state);
$form['widget'] = [
'#type' => 'radios',
'#title' => $this->t('Widget'),
'#options' => [
'autocomplete' => $this->t('Autocomplete'),
'select' => $this->t('Select'),
],
'#default_value' => $this->options['widget'],
'#required' => TRUE,
'#description' => $this->t('Choose what widget to use in order to specify entity.'),
];
if ($this->entityTypeManager->getDefinition($this->definition['entity_type'])->hasKey('bundle')) {
$options = array_map(function($item) {
return $item['label'];
}, $this->entityTypeBundleInfo->getBundleInfo($this->definition['entity_type']));
$form['target_bundles'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Bundles'),
'#description' => $this->t('Limit the possible values down to specific subset of bundles. Leave empty to have no filter by bundles.'),
'#default_value' => $this->options['target_bundles'],
'#options' => $options,
];
}
}
/**
* {@inheritdoc}
*/
public function submitExtraOptionsForm($form, FormStateInterface $form_state) {
parent::submitExtraOptionsForm($form, $form_state);
if (isset($form['target_bundles'])) {
$target_bundles = array_values(array_filter($form_state->getValue($form['target_bundles']['#parents'])));
if (empty($target_bundles)) {
$target_bundles = NULL;
}
$form_state->setValueForElement($form['target_bundles'], $target_bundles);
}
}
/**
* {@inheritdoc}
*/
public function hasExtraOptions() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function validateExposed(&$form, FormStateInterface $form_state) {
parent::validateExposed($form, $form_state);
$identifier = $this->options['expose']['identifier'];
$target_ids = $form_state->getValue($identifier);
switch ($this->operator) {
case 'IN':
$target_ids = array_map(function($item) {
return $item['target_id'];
}, $target_ids);
break;
case '=':
$target_ids = $target_ids['target_id'];
break;
}
$form_state->setValue($identifier, $target_ids);
}
/**
* {@inheritdoc}
*/
public function adminSummary() {
if ($this->isExposed()) {
return $this->t('Exposed');
}
$labels = [];
if (!empty($this->value)) {
foreach ($this->getEntityStorage()->loadMultiple($this->value) as $entity) {
$labels[] = $entity->label();
}
}
return $this->operator . ' ' . implode(', ', $labels);
}
/**
* {@inheritdoc}
*/
protected function valueForm(&$form, FormStateInterface $form_state) {
if (!empty($this->value)) {
$default_value = $this->getEntityStorage()->loadMultiple($this->value);
}
else {
$default_value = [];
}
switch ($this->options['widget']) {
case 'autocomplete':
$form['value'] = [
'#type' => 'synonyms_entity_autocomplete',
'#title' => $this->t('Entity'),
'#target_type' => $this->definition['entity_type'],
'#target_bundles' => $this->options['target_bundles'],
'#default_value' => $default_value,
];
break;
case 'select':
$form['value'] = [
'#type' => 'synonyms_entity_select',
'#title' => $this->t('Entity'),
'#target_type' => $this->definition['entity_type'],
'#handler_settings' => [
'target_bundles' => $this->options['target_bundles'],
],
'#default_value' => array_keys($default_value),
'#multiple' => TRUE,
'#empty_option' => '',
];
break;
}
}
/**
* {@inheritdoc}
*/
protected function valueSubmit($form, FormStateInterface $form_state) {
parent::valueSubmit($form, $form_state);
$target_ids = array_map(function($item) {
return $item['target_id'];
}, $form_state->getValue($form['value']['#parents']));
$form_state->setValueForElement($form['value'], $target_ids);
}
/**
* Get entity storage of the entity type this filter is set up to use.
*
* @return \Drupal\Core\Entity\EntityStorageInterface
*/
protected function getEntityStorage() {
return $this->entityTypeManager->getStorage($this->definition['entity_type']);
}
}
@@ -0,0 +1,72 @@
<?php
namespace Drupal\synonyms;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsProviderInterface;
/**
* Interface of synonyms configuration entity.
*/
interface SynonymInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
/**
* Get ID of the synonyms provider plugin that is set up in this entity.
*
* @return string
* Plugin ID of synonyms provider that corresponds to this configuration
* entity
*/
public function getProviderPlugin();
/**
* Get instance of the synonyms provider plugin that is set up in this entity.
*
* @return SynonymsProviderInterface
* Initiated synonyms provider instance that corresponds to this
* configuration entity
*/
public function getProviderPluginInstance();
/**
* Set the synonyms provider plugin to use in this entity.
*
* @param string $plugin
* Synonyms provider plugin ID to set in this configuration entity
*/
public function setProviderPlugin($plugin);
/**
* Get synonyms provider plugin configuration from this entity.
*
* @return array
* Array of synonyms provider plugin configuration
*/
public function getProviderConfiguration();
/**
* Set synonyms provider plugin configuration for this entity.
*
* @param array $provider_configuration
* Array of synonyms provider plugin configuration to set
*/
public function setProviderConfiguration(array $provider_configuration);
/**
* Get synonyms behavior configuration from this entity.
*
* @return array
* Array of synonyms behavior configuration
*/
public function getBehaviorConfiguration();
/**
* Set synonyms behavior configuration for this entity.
*
* @param array $behavior_configuration
* Array of synonyms behavior configuration to set
*/
public function setBehaviorConfiguration(array $behavior_configuration);
}
@@ -0,0 +1,21 @@
<?php
namespace Drupal\synonyms;
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
/**
* Provides a collection of synonym provider plugins.
*/
class SynonymProviderPluginCollection extends DefaultSingleLazyPluginCollection {
/**
* {@inheritdoc}
*
* @return \Drupal\synonyms\SynonymsProviderInterface\SynonymsProviderInterface
*/
public function &get($instance_id) {
return parent::get($instance_id);
}
}
@@ -0,0 +1,58 @@
<?php
namespace Drupal\synonyms\SynonymsProviderInterface;
use Drupal\Core\Database\Query\ConditionInterface;
/**
* Interface to look up entities by synonyms they have.
*/
interface SynonymsFindProviderInterface extends SynonymsProviderInterface {
/**
* Constant which denotes placeholder of a synonym column.
*
* @var string
*/
const COLUMN_SYNONYM_PLACEHOLDER = '***SYNONYM_COLUMN***';
/**
* Constant which denotes placeholder of an entity ID column.
*
* @var string
*/
const COLUMN_ENTITY_ID_PLACEHOLDER = '***ENTITY_ID***';
/**
* Look up entities by their synonyms within a behavior implementation.
*
* You are provided with a SQL condition that you should apply to the storage
* of synonyms within the provided behavior implementation. And then return
* result: what entities are matched by the provided condition through what
* synonyms.
*
* @param \Drupal\Core\Database\Query\ConditionInterface $condition
* Condition that defines what to search for. Apart from normal SQL
* conditions as known in Drupal, it may contain the following placeholders:
* - SynonymsFindProviderInterface::COLUMN_SYNONYM_PLACEHOLDER: to denote
* synonyms column which you should replace with the actual column name
* where the synonyms data for your provider is stored in plain text.
* - SynonymsFindProviderInterface::COLUMN_ENTITY_ID_PLACEHOLDER: to denote
* column that holds entity ID. You are supposed to replace this
* placeholder with actual column name that holds entity ID in your case.
* For ease of work with these placeholders, you may use the
* SynonymsFindTrait and then just invoke the
* $this->synonymsFindProcessCondition() method, so you won't have to worry
* much about it
*
* @return \Traversable
* Traversable result set of found synonyms and entity IDs to which those
* belong. Each element in the result set should be an object and should
* have the following structure:
* - synonym: (string) Synonym that was found and which satisfies the
* provided condition
* - entity_id: (int) ID of the entity to which the found synonym belongs
*/
public function synonymsFind(ConditionInterface $condition);
}
@@ -0,0 +1,45 @@
<?php
namespace Drupal\synonyms\SynonymsProviderInterface;
use Drupal\Core\Database\Query\ConditionInterface;
/**
* Supportive trait to find synonyms.
*/
trait SynonymsFindTrait {
/**
* Supportive method to process $condition argument in synonymsFind().
*
* This method will swap SynonymsFindProviderInterface::COLUMN_* to real
* column names in $condition for you, so you do not have to worry about
* internal processing of $condition object.
*
* @param \Drupal\Core\Database\Query\ConditionInterface $condition
* Condition to be processed
* @param string $synonym_column
* Actual name of the column where synonyms are kept in text
* @param string $entity_id_column
* Actual name of the column where entity_ids are kept
*/
public function synonymsFindProcessCondition(ConditionInterface $condition, $synonym_column, $entity_id_column) {
$condition_array = &$condition->conditions();
foreach ($condition_array as &$v) {
if (is_array($v) && isset($v['field'])) {
if ($v['field'] instanceof ConditionInterface) {
// Recursively process this condition too.
$this->synonymsFindProcessCondition($v['field'], $synonym_column, $entity_id_column);
}
else {
$replace = [
SynonymsFindProviderInterface::COLUMN_SYNONYM_PLACEHOLDER => $synonym_column,
SynonymsFindProviderInterface::COLUMN_ENTITY_ID_PLACEHOLDER => $entity_id_column,
];
$v['field'] = str_replace(array_keys($replace), array_values($replace), $v['field']);
}
}
}
}
}
@@ -0,0 +1,35 @@
<?php
namespace Drupal\synonyms\SynonymsProviderInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\synonyms\SynonymInterface;
/**
* Interface to format a synonym into some kind of wording.
*/
interface SynonymsFormatWordingProviderInterface {
/**
* Format a synonym into wording as requested by configuration.
*
* @param string $synonym
* Synonym that should be formatted
* @param ContentEntityInterface $entity
* Entity to which this synonym belongs
* @param SynonymInterface $synonym_config
* Synonym config entity in the context of which it all happens
*
* @return string
* Formatted wording
*/
public function synonymFormatWording($synonym, ContentEntityInterface $entity, SynonymInterface $synonym_config);
/**
* @return array
* Array of supported tokens in wording. Keys are the tokens whereas
* corresponding values are explanations about what each token means
*/
public function formatWordingAvailableTokens();
}
@@ -0,0 +1,51 @@
<?php
namespace Drupal\synonyms\SynonymsProviderInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\synonyms\SynonymInterface;
/**
* Trait to format wording of a synonym.
*/
trait SynonymsFormatWordingTrait {
/**
* Format a synonym into wording as requested by configuration.
*
* @param string $synonym
* Synonym that should be formatted
* @param ContentEntityInterface $entity
* Entity to which this synonym belongs
* @param SynonymInterface $synonym_config
* Synonym config entity in the context of which it all happens
*
* @return string
* Formatted wording
*/
public function synonymFormatWording($synonym, ContentEntityInterface $entity, SynonymInterface $synonym_config) {
// TODO: maybe we should use tokens replacement here? But then it would mean
// an extra dependency on the tokens module. Is it worth it? For now let's
// use stupid str_replace() and incorporate tokens only if user base really
// asks for it.
$map = [
'@synonym' => $synonym,
'@entity_label' => $entity->label(),
];
return str_replace(array_keys($map), array_values($map), $synonym_config->getBehaviorConfiguration()['wording']);
}
/**
* @return array
* Array of supported tokens in wording. Keys are the tokens whereas
* corresponding values are explanations about what each token means
*/
public function formatWordingAvailableTokens() {
return [
'@synonym' => $this->t('actual synonym value'),
'@entity_label' => $this->t('actual label of the entity this synonym belongs to'),
];
}
}
@@ -0,0 +1,45 @@
<?php
namespace Drupal\synonyms\SynonymsProviderInterface;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\synonyms\SynonymsBehavior\SynonymsBehaviorInterface;
/**
* Interface to extract (get) synonyms from an entity.
*/
interface SynonymsGetProviderInterface extends SynonymsProviderInterface {
/**
* Fetch synonyms from an entity.
*
* @param ContentEntityInterface $entity
* Entity whose synonyms should be fetched
* @param array $behavior_configuration
* Configuration of the synonyms behavior that this provider should obey to
*
* @return string[]
* Array of extracted synonyms
*/
public function getSynonyms(ContentEntityInterface $entity, array $behavior_configuration = []);
/**
* Fetch synonyms from multiple entities at once.
*
* @param ContentEntityInterface[] $entities
* Array of entities whose synonyms should be fetched. The array will be
* keyed by entity ID and all provided entities will be of the same entity
* type and bundle
* @param array $behavior_configuration
* Configuration of the synonyms behavior that this provider should obey to
*
* @return array
* Array of extracted synonyms. It must be keyed by entity ID and each sub
* array should represent a list of synonyms that were extracted from the
* corresponding entity
*/
public function getSynonymsMultiple(array $entities, array $behavior_configuration = []);
}
@@ -0,0 +1,50 @@
<?php
namespace Drupal\synonyms\SynonymsProviderInterface;
use Drupal\Core\Entity\ContentEntityInterface;
/**
* Trait to extract synonyms from an entity.
*/
trait SynonymsGetTrait {
/**
* Fetch synonyms from multiple entities at once.
*
* @param ContentEntityInterface[] $entities
* Array of entities whose synonyms should be fetched. They array will be
* keyed by entity ID and all provided entities will be of the same entity
* type and bundle
* @param array $behavior_configuration
* Configuration of the synonyms behavior that this provider should obey to
*
* @return array
* Array of extracted synonyms. It must be keyed by entity ID and each sub
* array should represent a list of synonyms that were extracted from the
* corresponding entity
*/
public function getSynonymsMultiple(array $entities, array $behavior_configuration = []) {
$synonyms = [];
foreach ($entities as $entity_id => $entity) {
$synonyms[$entity_id] = $this->getSynonyms($entity, $behavior_configuration);
}
return $synonyms;
}
/**
* Fetch synonyms from an entity.
*
* @param ContentEntityInterface $entity
* Entity whose synonyms should be fetched
* @param array $behavior_configuration
* Configuration of the synonyms behavior that this provider should obey to
*
* @return string[]
* Array of extracted synonyms
*/
abstract public function getSynonyms(ContentEntityInterface $entity, array $behavior_configuration = []);
}
@@ -0,0 +1,27 @@
<?php
namespace Drupal\synonyms\SynonymsProviderInterface;
use Drupal\synonyms\SynonymsService\Behavior\SynonymsBehaviorInterface;
/**
* Most generic synonyms provider interface. All providers must implement it.
*/
interface SynonymsProviderInterface {
/**
* Fetch behavior service which corresponds to this provider.
*
* @return string
* Name of synonyms behavior service which corresponds to this provider
*/
public function getBehaviorService();
/**
* Fetch behavior service instance which corresponds to this provider.
*
* @return SynonymsBehaviorInterface
*/
public function getBehaviorServiceInstance();
}
@@ -0,0 +1,31 @@
<?php
namespace Drupal\synonyms;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\synonyms\Annotation\SynonymsProvider;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsProviderInterface;
/**
* Plugin manager for Synonyms provider plugin type.
*/
class SynonymsProviderPluginManager extends DefaultPluginManager {
/**
* {@inheritdoc}
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct(
'Plugin/Synonyms/Provider',
$namespaces,
$module_handler,
SynonymsProviderInterface::class,
SynonymsProvider::class
);
$this->alterInfo('synonyms_provider_info');
$this->setCacheBackend($cache_backend, 'synonyms_provider_info_plugins');
}
}
@@ -0,0 +1,250 @@
<?php
namespace Drupal\synonyms\SynonymsService\Behavior;
use Drupal\Component\Utility\Html;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\synonyms\SynonymInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFormatWordingProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetProviderInterface;
use Drupal\synonyms\SynonymsService\BehaviorService;
/**
* Synonyms behavior service for autocomplete.
*/
class AutocompleteService implements SynonymsBehaviorConfigurableInterface {
use StringTranslationTrait;
/**
* @var KeyValueStoreInterface
*/
protected $keyValue;
/**
* The entity reference selection handler plugin manager.
*
* @var SelectionPluginManagerInterface
*/
protected $selectionManager;
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* @var Connection
*/
protected $database;
/**
* @var RendererInterface
*/
protected $renderer;
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
public function __construct(KeyValueFactoryInterface $key_value, SelectionPluginManagerInterface $selection_plugin_manager, BehaviorService $behavior_service, Connection $database, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer) {
$this->keyValue = $key_value->get('synonyms_entity_autocomplete');
$this->selectionManager = $selection_plugin_manager;
$this->behaviorService = $behavior_service;
$this->database = $database;
$this->entityTypeManager = $entity_type_manager;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, array $configuration, SynonymInterface $synonym_config) {
$replacements = [
'#theme' => 'item_list',
'#list_type' => 'ul',
'#items' => [],
];
foreach ($synonym_config->getProviderPluginInstance()->formatWordingAvailableTokens() as $token => $token_info) {
$replacements['#items'][] = Html::escape($token) . ': ' . $token_info;
}
$replacements = $this->renderer->renderRoot($replacements);
$form['wording'] = [
'#type' => 'textfield',
'#title' => $this->t('Wording for autocomplete suggestion'),
'#default_value' => $configuration['wording'],
'#description' => $this->t('Specify the wording with which the autocomplete suggestion should be presented. Available replacement tokens are: @replacements', [
'@replacements' => $replacements,
]),
'#required' => TRUE,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state, SynonymInterface $synonym_config) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state, SynonymInterface $synonym_config) {
return [
'wording' => $form_state->getValue('wording'),
];
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Autocomplete');
}
/**
* {@inheritdoc}
*/
public function getRequiredInterfaces() {
return [
SynonymsGetProviderInterface::class,
SynonymsFindProviderInterface::class,
SynonymsFormatWordingProviderInterface::class,
];
}
/**
* Execute synonym-friendly lookup of entities by a given keyword.
*
* @param string $keyword
* Keyword to search for
* @param string $key_value_key
* Key under which additional settings about the lookup are stored in
* key-value storage
*
* @return array
* Array of looked up suggestions. Each array will have the following
* structure:
* - entity_id: (int) ID of the entity which this entry represents
* - entity_label: (string) Label of the entity which this entry represents
* - wording: (string) Wording with which this entry should be shown to the
* end user on the UI
*/
public function autocompleteLookup($keyword, $key_value_key) {
$suggestions = [];
if ($this->keyValue->has($key_value_key)) {
$settings = $this->keyValue->get($key_value_key);
$suggested_entity_ids = [];
$target_bundles = $settings['target_bundles'];
$handler_settings = [];
if (!empty($target_bundles)) {
$handler_settings['target_bundles'] = $target_bundles;
}
elseif (!$this->entityTypeManager->getDefinition($settings['target_type'])->hasKey('bundle')) {
$target_bundles = [$settings['target_type']];
}
$options = [
'target_type' => $settings['target_type'],
'handler' => 'default',
'handler_settings' => $handler_settings,
];
$handler = $this->selectionManager->getInstance($options);
foreach ($handler->getReferenceableEntities($keyword, $settings['match'], $settings['suggestion_size']) as $suggested_entities) {
foreach ($suggested_entities as $entity_id => $entity_label) {
$suggestions[] = [
'entity_id' => $entity_id,
'entity_label' => $entity_label,
'wording' => $entity_label,
];
if ($settings['suggest_only_unique']) {
$suggested_entity_ids[] = $entity_id;
}
}
}
if (count($suggestions) < $settings['suggestion_size']) {
foreach ($this->behaviorService->getSynonymConfigEntities('synonyms.behavior.autocomplete', $settings['target_type'], $target_bundles) as $behavior_service) {
$plugin_instance = $behavior_service->getProviderPluginInstance();
$condition = new Condition('AND');
switch ($settings['match']) {
case 'CONTAINS':
$condition->condition(SynonymsFindProviderInterface::COLUMN_SYNONYM_PLACEHOLDER, '%' . $this->database->escapeLike($keyword) . '%', 'LIKE');
break;
case 'STARTS_WITH':
$condition->condition(SynonymsFindProviderInterface::COLUMN_SYNONYM_PLACEHOLDER, $this->database->escapeLike($keyword) . '%', 'LIKE');
break;
}
if (!empty($suggested_entity_ids)) {
$condition->condition(SynonymsFindProviderInterface::COLUMN_ENTITY_ID_PLACEHOLDER, $suggested_entity_ids, 'NOT IN');
}
foreach ($plugin_instance->synonymsFind($condition) as $row) {
if (!in_array($row->entity_id, $suggested_entity_ids)) {
$suggestions[] = [
'entity_id' => $row->entity_id,
'entity_label' => NULL,
'synonym' => $row->synonym,
'synonym_config_entity' => $behavior_service,
'wording' => NULL,
];
}
if ($settings['suggest_only_unique']) {
$suggested_entity_ids[] = $row->entity_id;
}
if (count($suggestions) == $settings['suggestion_size']) {
break(2);
}
}
}
}
$ids = [];
foreach ($suggestions as $suggestion) {
if (!$suggestion['entity_label']) {
$ids[] = $suggestion['entity_id'];
}
}
$ids = array_unique($ids);
if (!empty($ids)) {
$entities = $this->entityTypeManager->getStorage($settings['target_type'])
->loadMultiple($ids);
foreach ($suggestions as $k => $suggestion) {
if (!$suggestion['entity_label']) {
$suggestions[$k]['entity_label'] = $entities[$suggestion['entity_id']]->label();
$suggestions[$k]['wording'] = $suggestion['synonym_config_entity']->getProviderPluginInstance()->synonymFormatWording($suggestion['synonym'], $entities[$suggestion['entity_id']], $suggestion['synonym_config_entity']);
}
}
}
}
return $suggestions;
}
}
@@ -0,0 +1,157 @@
<?php
namespace Drupal\synonyms\SynonymsService\Behavior;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\synonyms\SynonymInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFormatWordingProviderInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsGetProviderInterface;
use Drupal\synonyms\SynonymsService\BehaviorService;
/**
* Synonyms behavior service for select widget.
*/
class SelectService implements SynonymsBehaviorConfigurableInterface {
use StringTranslationTrait;
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* @var RendererInterface
*/
protected $renderer;
/**
* SelectService constructor.
*/
public function __construct(BehaviorService $behavior_service, RendererInterface $renderer) {
$this->behaviorService = $behavior_service;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, array $configuration, SynonymInterface $synonym_config) {
$replacements = [
'#theme' => 'item_list',
'#list_type' => 'ul',
'#items' => [],
];
foreach ($synonym_config->getProviderPluginInstance()->formatWordingAvailableTokens() as $token => $token_info) {
$replacements['#items'][] = Html::escape($token) . ': ' . $token_info;
}
$replacements = $this->renderer->renderRoot($replacements);
$form['wording'] = [
'#type' => 'textfield',
'#title' => $this->t('Wording for select entry'),
'#default_value' => $configuration['wording'],
'#description' => $this->t('Specify the wording with which the select entry should be presented. Available replacement tokens are: @replacements', [
'@replacements' => $replacements,
]),
'#required' => TRUE,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state, SynonymInterface $synonym_config) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state, SynonymInterface $synonym_config) {
return [
'wording' => $form_state->getValue('wording'),
];
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->t('Select');
}
/**
* {@inheritdoc}
*/
public function getRequiredInterfaces() {
return [
SynonymsGetProviderInterface::class,
SynonymsFormatWordingProviderInterface::class,
];
}
/**
* Extract a list of synonyms from an entity.
*
* @param ContentEntityInterface $entity
* Entity from which to extract the synonyms
*
* @return array
* Array of synonyms. Each sub array will have the following structure:
* - synonym: (string) Synonym itself
* - wording: (string) Formatted wording with which this synonym should be
* presented to the end user
*/
public function getSynonyms(ContentEntityInterface $entity) {
$synonyms = $this->getSynonymsMultiple([$entity->id() => $entity]);
return $synonyms[$entity->id()];
}
/**
* Extract a list of synonyms from multiple entities.
*
* @param ContentEntityInterface[] $entities
* Array of entities from which to extract the synonyms. It should be keyed
* by entity ID and may only contain entities of the same type and bundle
*
* @return array
* Array of synonyms. The returned array will be keyed by entity ID and the
* inner array will have the following structure:
* - synonym: (string) Synonym itself
* - wording: (string) Formatted wording with which this synonym should be
* presented to the end user
*/
public function getSynonymsMultiple(array $entities) {
if (empty($entities)) {
return [];
}
$synonym_configs = $this->behaviorService->getSynonymConfigEntities('synonyms.behavior.select', reset($entities)->getEntityTypeId(), reset($entities)->bundle());
$synonyms = [];
foreach ($entities as $entity) {
$synonyms[$entity->id()] = [];
}
foreach ($synonym_configs as $synonym_config) {
foreach ($synonym_config->getProviderPluginInstance()->getSynonymsMultiple($entities, $synonym_config->getBehaviorConfiguration()) as $entity_id => $entity_synonyms) {
foreach ($entity_synonyms as $entity_synonym) {
$synonyms[$entity_id][] = [
'synonym' => $entity_synonym,
'wording' => $synonym_config->getProviderPluginInstance()->synonymFormatWording($entity_synonym, $entities[$entity_id], $synonym_config),
];
}
}
}
return $synonyms;
}
}
@@ -0,0 +1,65 @@
<?php
namespace Drupal\synonyms\SynonymsService\Behavior;
use Drupal\Core\Form\FormStateInterface;
use Drupal\synonyms\SynonymInterface;
/**
* Interface of a configurable synonyms behavior.
*/
interface SynonymsBehaviorConfigurableInterface extends SynonymsBehaviorInterface {
/**
* Build configuration form.
*
* @param array $form
* Form into which your configuration form will be embedded. You are
* supposed to extend this array with additional configuration form elements
* that your behavior needs
* @param FormStateInterface $form_state
* Form state object that corresponds to this form
* @param array $configuration
* Array of existing configuration for your behavior. Normally you would use
* it as a source of default values for your configuration form elements
* @param SynonymInterface $synonym_config
* Synonym config entity in the context of which the form is being built
*
* @return array
* Extended $form that includes the form elements required for configuration
* of your behavior
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, array $configuration, SynonymInterface $synonym_config);
/**
* Validate submitted values into your configuration form.
*
* @param array $form
* Your configuration form as it was built in
* static::buildConfigurationForm()
* @param FormStateInterface $form_state
* Form state that corresponds to this form. You should rise form validation
* errors on this form state, should you discover any in user input
* @param SynonymInterface $synonym_config
* Synonym config entity in the context of which the form is being built
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state, SynonymInterface $synonym_config);
/**
* Process submitted values and generate new configuration.
*
* @param array $form
* Your configuration form as it was built in
* static::buildConfigurationForm()
* @param FormStateInterface $form_state
* Form state that corresponds to this form
* @param SynonymInterface $synonym_config
* Synonym config entity in the context of which the form is being built
*
* @return array
* Array of new behavior configuration that corresponds to the submitted
* values in the form
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state, SynonymInterface $synonym_config);
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\synonyms\SynonymsService\Behavior;
/**
* Interface of a synonyms behavior. All behaviors must implement it.
*/
interface SynonymsBehaviorInterface {
/**
* Get human readable title of this behavior.
*
* @return string
*/
public function getTitle();
/**
* Get a list of interfaces required from synonyms provider plugins.
*
* Get a list of PHP interfaces a synonyms provider plugin must implement in
* order to support this behavior.
*
* @return array
* Array of PHP interfaces a synonyms provider plugin must implement in
* order to support this behavior
*/
public function getRequiredInterfaces();
}
@@ -0,0 +1,143 @@
<?php
namespace Drupal\synonyms\SynonymsService;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\synonyms\Entity\Synonym;
use Drupal\synonyms\SynonymsService\Behavior\SynonymsBehaviorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Collect all known synonyms behavior services.
*
* Collect all known synonyms behavior services during dependency injection
* container compilation.
*/
class BehaviorService implements ContainerInjectionInterface {
/**
* @var array
*/
protected $behaviorServices;
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->behaviorServices = [];
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')
);
}
/**
* Add a new discovered behavior service.
*
* @param SynonymsBehaviorInterface $behavior_service
* Behavior service object that was discovered and should be added into the
* list of known ones
* @param string $id
* Service ID that corresponds to this behavior service
*/
public function addBehaviorService(SynonymsBehaviorInterface $behavior_service, $id) {
if (!isset($this->behaviorServices[$id])) {
$this->behaviorServices[$id] = [
'service' => $behavior_service,
'required_interfaces' => [],
];
}
$this->behaviorServices[$id]['required_interfaces'] = $behavior_service->getRequiredInterfaces();
}
/**
* Array of known synonyms behavior service.
*
* @return array
*/
public function getBehaviorServices() {
return $this->behaviorServices;
}
/**
* Get a synonyms behavior service.
*
* @param string $behavior_service_id
* ID of the service to retrieve
*
* @return array
* Array of information about the behavior service. The array will have the
* following structure:
* - service: (SynonymsBehaviorInterface) Initiated behavior service
* - required_interfaces: (array) Array of PHP interfaces it requires
*/
public function getBehaviorService($behavior_service_id) {
return isset($this->behaviorServices[$behavior_service_id]) ? $this->behaviorServices[$behavior_service_id] : NULL;
}
/**
* Get a list of behavior services that require a given interface.
*
* @param string $interface
* Full name of the interface
*
* @return array
* The return structure of this method is identical of the return structure
* of static::getBehaviorServices()
*/
public function getBehaviorServicesWithInterface($interface) {
$return = [];
foreach ($this->getBehaviorServices() as $service_id => $service) {
if (in_array($interface, $service['required_interfaces'])) {
$return[$service_id] = $service;
}
}
return $return;
}
/**
* Get a list of enabled synonym providers for a requested synonyms behavior.
*
* @param string $synonyms_behavior
* ID of the synonyms behavior services whose enabled providers should be
* returned
* @param string $entity_type
* Entity type for which to conduct the search
* @param string|array $bundle
* Single bundle or an array of them for which to conduct the search. If
* null is given, then no restrictions are applied on bundle level
*
* @return Synonym[]
*/
public function getSynonymConfigEntities($synonyms_behavior, $entity_type, $bundle) {
$entities = [];
if (is_scalar($bundle) && !is_null($bundle)) {
$bundle = [$bundle];
}
foreach ($this->entityTypeManager->getStorage('synonym')->loadMultiple() as $entity) {
$provider_instance = $entity->getProviderPluginInstance();
$provider_definition = $provider_instance->getPluginDefinition();
if ($provider_definition['synonyms_behavior_service'] == $synonyms_behavior && $provider_definition['controlled_entity_type'] == $entity_type && (!is_array($bundle) || in_array($provider_definition['controlled_bundle'], $bundle))) {
$entities[] = $entity;
}
}
return $entities;
}
}
@@ -0,0 +1,79 @@
<?php
namespace Drupal\synonyms\SynonymsService;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindProviderInterface;
/**
* Service to look up an entity by its name or synonym.
*/
class EntityGet {
/**
* @var SynonymsFind
*/
protected $synonymsFindService;
/**
* @var EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var QueryFactory
*/
protected $queryFactory;
public function __construct(SynonymsFind $synonyms_find_service, EntityTypeManagerInterface $entity_type_manager, QueryFactory $query_factory) {
$this->synonymsFindService = $synonyms_find_service;
$this->entityTypeManager = $entity_type_manager;
$this->queryFactory = $query_factory;
}
/**
* Try finding an entity by its name or synonym.
*
* @param EntityTypeInterface $entity_type
* What entity type is being searched
* @param string $name
* The look up keyword (the supposed name or synonym)
* @param string $bundle
* Optionally limit the search within a specific bundle name of the provided
* entity type
*
* @return int
* ID of the looked up entity. If such entity was not found, then 0 is
* returned
*/
public function entityGetBySynonym(EntityTypeInterface $entity_type, $name, $bundle = NULL) {
if ($entity_type->id() == 'user' || $entity_type->hasKey('label')) {
$label_column = $entity_type->id() == 'user' ? 'name' : $entity_type->getKey('label');
$query = $this->queryFactory->get($entity_type->id());
$query->condition($label_column, $name);
if ($entity_type->hasKey('bundle') && $bundle) {
$query->condition($entity_type->getKey('bundle'), $bundle);
}
$result = $query->execute();
$result = reset($result);
if ($result) {
return $result;
}
}
$condition = new Condition('AND');
$condition->condition(SynonymsFindProviderInterface::COLUMN_SYNONYM_PLACEHOLDER, $name);
$synonyms_found = $this->synonymsFindService->synonymsFind($condition, $entity_type, $bundle, '*');
if (isset($synonyms_found[0]->entity_id)) {
return $synonyms_found[0]->entity_id;
}
return 0;
}
}
@@ -0,0 +1,44 @@
<?php
namespace Drupal\synonyms\SynonymsService;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Service to map known field types to how synonyms are encoded in them.
*/
class FieldTypeToSynonyms {
/**
* @var ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* FieldTypeToSynonyms constructor.
*/
public function __construct(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* Map field types to the properties within them where synonyms are stored.
*
* @return array
* Map where keys are simple field types and values are the properties where
* the corresponding field type keeps the synonyms
*/
public function getSimpleFieldTypeToPropertyMap() {
$map = [
'integer' => 'value',
'float' => 'value',
'decimal' => 'value',
'string' => 'value',
'email' => 'value',
'telephone' => 'value',
];
$this->moduleHandler->alter('synonyms_field_type_to_synonym', $map);
return $map;
}
}
@@ -0,0 +1,84 @@
<?php
namespace Drupal\synonyms\SynonymsService;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\synonyms\SynonymsProviderInterface\SynonymsFindProviderInterface;
/**
* Service that allows to look up entities by their synonyms.
*/
class SynonymsFind {
/**
* @var BehaviorService
*/
protected $behaviorService;
/**
* @var EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* SynonymsFind constructor.
*/
public function __construct(BehaviorService $behavior_service, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
$this->behaviorService = $behavior_service;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
* Lookup entity IDs by the $condition.
*
* @param Condition $condition
* Condition which defines what to search for
* @param EntityTypeInterface $entity_type
* Entity type within which to search
* @param string|array $bundle
* Either single bundle string or array of such within which to search. NULL
* stands for no filtering by bundle, i.e. searching among all bundles
* @param string|array $service_id
* Either a single behavior service ID or an array of them within which to
* execute the lookup. You may also use the wildcard * to search among all
* supported behaviors
*
* @return array
* Array of looked up synonyms/entities. Each element in this array will be
* an object with the following structure:
* - synonym: (string) synonym that was looked up
* - entity_id: (int) ID of the entity which this synonym belongs to
*/
public function synonymsFind(Condition $condition, EntityTypeInterface $entity_type, $bundle = NULL, $service_id = 'synonyms.behavior.autocomplete') {
if (!$entity_type->getKey('bundle')) {
$bundle = $entity_type->id();
}
$lookup = [];
if ($service_id == '*') {
$service_id = array_keys($this->behaviorService->getBehaviorServicesWithInterface(SynonymsFindProviderInterface::class));
}
if (!is_array($service_id)) {
$service_id = [$service_id];
}
if (is_null($bundle)) {
$bundle = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type->id()));
}
foreach ($service_id as $service_id_value) {
foreach ($this->behaviorService->getSynonymConfigEntities($service_id_value, $entity_type->id(), $bundle) as $synonym_config) {
foreach ($synonym_config->getProviderPluginInstance()->synonymsFind(clone $condition) as $synonym) {
$lookup[] = $synonym;
}
}
}
return $lookup;
}
}