EntityReferenceAutocompleteWidget.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <?php
  2. namespace Drupal\Core\Field\Plugin\Field\FieldWidget;
  3. use Drupal\Core\Field\FieldItemListInterface;
  4. use Drupal\Core\Field\WidgetBase;
  5. use Drupal\Core\Form\FormStateInterface;
  6. use Drupal\user\EntityOwnerInterface;
  7. use Symfony\Component\Validator\ConstraintViolationInterface;
  8. /**
  9. * Plugin implementation of the 'entity_reference_autocomplete' widget.
  10. *
  11. * @FieldWidget(
  12. * id = "entity_reference_autocomplete",
  13. * label = @Translation("Autocomplete"),
  14. * description = @Translation("An autocomplete text field."),
  15. * field_types = {
  16. * "entity_reference"
  17. * }
  18. * )
  19. */
  20. class EntityReferenceAutocompleteWidget extends WidgetBase {
  21. /**
  22. * {@inheritdoc}
  23. */
  24. public static function defaultSettings() {
  25. return [
  26. 'match_operator' => 'CONTAINS',
  27. 'match_limit' => 10,
  28. 'size' => 60,
  29. 'placeholder' => '',
  30. ] + parent::defaultSettings();
  31. }
  32. /**
  33. * {@inheritdoc}
  34. */
  35. public function settingsForm(array $form, FormStateInterface $form_state) {
  36. $element['match_operator'] = [
  37. '#type' => 'radios',
  38. '#title' => t('Autocomplete matching'),
  39. '#default_value' => $this->getSetting('match_operator'),
  40. '#options' => $this->getMatchOperatorOptions(),
  41. '#description' => t('Select the method used to collect autocomplete suggestions. Note that <em>Contains</em> can cause performance issues on sites with thousands of entities.'),
  42. ];
  43. $element['match_limit'] = [
  44. '#type' => 'number',
  45. '#title' => $this->t('Number of results'),
  46. '#default_value' => $this->getSetting('match_limit'),
  47. '#min' => 0,
  48. '#description' => $this->t('The number of suggestions that will be listed. Use <em>0</em> to remove the limit.'),
  49. ];
  50. $element['size'] = [
  51. '#type' => 'number',
  52. '#title' => t('Size of textfield'),
  53. '#default_value' => $this->getSetting('size'),
  54. '#min' => 1,
  55. '#required' => TRUE,
  56. ];
  57. $element['placeholder'] = [
  58. '#type' => 'textfield',
  59. '#title' => t('Placeholder'),
  60. '#default_value' => $this->getSetting('placeholder'),
  61. '#description' => t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
  62. ];
  63. return $element;
  64. }
  65. /**
  66. * {@inheritdoc}
  67. */
  68. public function settingsSummary() {
  69. $summary = [];
  70. $operators = $this->getMatchOperatorOptions();
  71. $summary[] = t('Autocomplete matching: @match_operator', ['@match_operator' => $operators[$this->getSetting('match_operator')]]);
  72. $size = $this->getSetting('match_limit') ?: $this->t('unlimited');
  73. $summary[] = $this->t('Autocomplete suggestion list size: @size', ['@size' => $size]);
  74. $summary[] = t('Textfield size: @size', ['@size' => $this->getSetting('size')]);
  75. $placeholder = $this->getSetting('placeholder');
  76. if (!empty($placeholder)) {
  77. $summary[] = t('Placeholder: @placeholder', ['@placeholder' => $placeholder]);
  78. }
  79. else {
  80. $summary[] = t('No placeholder');
  81. }
  82. return $summary;
  83. }
  84. /**
  85. * {@inheritdoc}
  86. */
  87. public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
  88. $entity = $items->getEntity();
  89. $referenced_entities = $items->referencedEntities();
  90. // Append the match operation to the selection settings.
  91. $selection_settings = $this->getFieldSetting('handler_settings') + [
  92. 'match_operator' => $this->getSetting('match_operator'),
  93. 'match_limit' => $this->getSetting('match_limit'),
  94. ];
  95. $element += [
  96. '#type' => 'entity_autocomplete',
  97. '#target_type' => $this->getFieldSetting('target_type'),
  98. '#selection_handler' => $this->getFieldSetting('handler'),
  99. '#selection_settings' => $selection_settings,
  100. // Entity reference field items are handling validation themselves via
  101. // the 'ValidReference' constraint.
  102. '#validate_reference' => FALSE,
  103. '#maxlength' => 1024,
  104. '#default_value' => isset($referenced_entities[$delta]) ? $referenced_entities[$delta] : NULL,
  105. '#size' => $this->getSetting('size'),
  106. '#placeholder' => $this->getSetting('placeholder'),
  107. ];
  108. if ($bundle = $this->getAutocreateBundle()) {
  109. $element['#autocreate'] = [
  110. 'bundle' => $bundle,
  111. 'uid' => ($entity instanceof EntityOwnerInterface) ? $entity->getOwnerId() : \Drupal::currentUser()->id(),
  112. ];
  113. }
  114. return ['target_id' => $element];
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) {
  120. return $element['target_id'];
  121. }
  122. /**
  123. * {@inheritdoc}
  124. */
  125. public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
  126. foreach ($values as $key => $value) {
  127. // The entity_autocomplete form element returns an array when an entity
  128. // was "autocreated", so we need to move it up a level.
  129. if (is_array($value['target_id'])) {
  130. unset($values[$key]['target_id']);
  131. $values[$key] += $value['target_id'];
  132. }
  133. }
  134. return $values;
  135. }
  136. /**
  137. * Returns the name of the bundle which will be used for autocreated entities.
  138. *
  139. * @return string
  140. * The bundle name. If autocreate is not active, NULL will be returned.
  141. */
  142. protected function getAutocreateBundle() {
  143. $bundle = NULL;
  144. if ($this->getSelectionHandlerSetting('auto_create')) {
  145. $target_bundles = $this->getSelectionHandlerSetting('target_bundles');
  146. // If there's no target bundle at all, use the target_type. It's the
  147. // default for bundleless entity types.
  148. if (empty($target_bundles)) {
  149. $bundle = $this->getFieldSetting('target_type');
  150. }
  151. // If there's only one target bundle, use it.
  152. elseif (count($target_bundles) == 1) {
  153. $bundle = reset($target_bundles);
  154. }
  155. // If there's more than one target bundle, use the autocreate bundle
  156. // stored in selection handler settings.
  157. elseif (!$bundle = $this->getSelectionHandlerSetting('auto_create_bundle')) {
  158. // If no bundle has been set as auto create target means that there is
  159. // an inconsistency in entity reference field settings.
  160. trigger_error(sprintf(
  161. "The 'Create referenced entities if they don't already exist' option is enabled but a specific destination bundle is not set. You should re-visit and fix the settings of the '%s' (%s) field.",
  162. $this->fieldDefinition->getLabel(),
  163. $this->fieldDefinition->getName()
  164. ), E_USER_WARNING);
  165. }
  166. }
  167. return $bundle;
  168. }
  169. /**
  170. * Returns the value of a setting for the entity reference selection handler.
  171. *
  172. * @param string $setting_name
  173. * The setting name.
  174. *
  175. * @return mixed
  176. * The setting value.
  177. */
  178. protected function getSelectionHandlerSetting($setting_name) {
  179. $settings = $this->getFieldSetting('handler_settings');
  180. return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL;
  181. }
  182. /**
  183. * Returns the options for the match operator.
  184. *
  185. * @return array
  186. * List of options.
  187. */
  188. protected function getMatchOperatorOptions() {
  189. return [
  190. 'STARTS_WITH' => t('Starts with'),
  191. 'CONTAINS' => t('Contains'),
  192. ];
  193. }
  194. }