3 namespace Drupal\inline_entity_form\Plugin\Field\FieldWidget;
5 use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
8 use Drupal\Core\Entity\EntityTypeManagerInterface;
9 use Drupal\Core\Field\FieldDefinitionInterface;
10 use Drupal\Core\Field\FieldItemListInterface;
11 use Drupal\Core\Field\WidgetBase;
12 use Drupal\Core\Form\FormStateInterface;
13 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
14 use Drupal\inline_entity_form\TranslationHelper;
15 use Symfony\Component\DependencyInjection\ContainerInterface;
18 * Inline entity form widget base class.
20 abstract class InlineEntityFormBase extends WidgetBase implements ContainerFactoryPluginInterface {
23 * The inline entity form id.
30 * The entity type bundle info.
32 * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
34 protected $entityTypeBundleInfo;
37 * The entity type manager.
39 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
41 protected $entityTypeManager;
44 * The inline entity from handler.
46 * @var \Drupal\inline_entity_form\InlineFormInterface
48 protected $inlineFormHandler;
51 * The entity display repository.
53 * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
55 protected $entityDisplayRepository;
58 * Constructs an InlineEntityFormBase object.
60 * @param array $plugin_id
61 * The plugin_id for the widget.
62 * @param mixed $plugin_definition
63 * The plugin implementation definition.
64 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
65 * The definition of the field to which the widget is associated.
66 * @param array $settings
67 * The widget settings.
68 * @param array $third_party_settings
69 * Any third party settings.
70 * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
71 * The entity type bundle info.
72 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
73 * The entity type manager.
74 * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
75 * The entity display repository.
77 public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository) {
78 parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
80 $this->entityTypeBundleInfo = $entity_type_bundle_info;
81 $this->entityTypeManager = $entity_type_manager;
82 $this->entityDisplayRepository = $entity_display_repository;
83 $this->createInlineFormHandler();
89 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
93 $configuration['field_definition'],
94 $configuration['settings'],
95 $configuration['third_party_settings'],
96 $container->get('entity_type.bundle.info'),
97 $container->get('entity_type.manager'),
98 $container->get('entity_display.repository')
103 * Creates an instance of the inline form handler for the current entity type.
105 protected function createInlineFormHandler() {
106 if (!isset($this->inlineFormHandler)) {
107 $target_type = $this->getFieldSetting('target_type');
108 $this->inlineFormHandler = $this->entityTypeManager->getHandler($target_type, 'inline_form');
115 public function __sleep() {
116 $keys = array_diff(parent::__sleep(), ['inlineFormHandler']);
123 public function __wakeup() {
125 $this->createInlineFormHandler();
129 * Sets inline entity form ID.
131 * @param string $ief_id
132 * The inline entity form ID.
134 protected function setIefId($ief_id) {
135 $this->iefId = $ief_id;
139 * Gets inline entity form ID.
142 * Inline entity form ID.
144 protected function getIefId() {
149 * Gets the target bundles for the current field.
154 protected function getTargetBundles() {
155 $settings = $this->getFieldSettings();
156 if (!empty($settings['handler_settings']['target_bundles'])) {
157 $target_bundles = array_values($settings['handler_settings']['target_bundles']);
158 // Filter out target bundles which no longer exist.
159 $existing_bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($settings['target_type']));
160 $target_bundles = array_intersect($target_bundles, $existing_bundles);
163 // If no target bundles have been specified then all are available.
164 $target_bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($settings['target_type']));
167 return $target_bundles;
171 * Gets the bundles for which the current user has create access.
174 * The list of bundles.
176 protected function getCreateBundles() {
177 $create_bundles = [];
178 foreach ($this->getTargetBundles() as $bundle) {
179 if ($this->getAccessHandler()->createAccess($bundle)) {
180 $create_bundles[] = $bundle;
184 return $create_bundles;
190 public static function defaultSettings() {
192 'form_mode' => 'default',
193 'override_labels' => FALSE,
194 'label_singular' => '',
195 'label_plural' => '',
196 'collapsible' => FALSE,
197 'collapsed' => FALSE,
204 public function settingsForm(array $form, FormStateInterface $form_state) {
205 $entity_type_id = $this->getFieldSetting('target_type');
206 $states_prefix = 'fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings]';
208 $element['form_mode'] = [
210 '#title' => $this->t('Form mode'),
211 '#default_value' => $this->getSetting('form_mode'),
212 '#options' => $this->entityDisplayRepository->getFormModeOptions($entity_type_id),
215 $element['override_labels'] = [
216 '#type' => 'checkbox',
217 '#title' => $this->t('Override labels'),
218 '#default_value' => $this->getSetting('override_labels'),
220 $element['label_singular'] = [
221 '#type' => 'textfield',
222 '#title' => $this->t('Singular label'),
223 '#default_value' => $this->getSetting('label_singular'),
226 ':input[name="' . $states_prefix . '[override_labels]"]' => ['checked' => TRUE],
230 $element['label_plural'] = [
231 '#type' => 'textfield',
232 '#title' => $this->t('Plural label'),
233 '#default_value' => $this->getSetting('label_plural'),
236 ':input[name="' . $states_prefix . '[override_labels]"]' => ['checked' => TRUE],
240 $element['collapsible'] = [
241 '#type' => 'checkbox',
242 '#title' => $this->t('Collapsible'),
243 '#default_value' => $this->getSetting('collapsible'),
245 $element['collapsed'] = [
246 '#type' => 'checkbox',
247 '#title' => $this->t('Collapsed by default'),
248 '#default_value' => $this->getSetting('collapsed'),
251 ':input[name="' . $states_prefix . '[collapsible]"]' => ['checked' => TRUE],
262 public function settingsSummary() {
264 if ($entity_form_mode = $this->getEntityFormMode()) {
265 $form_mode_label = $entity_form_mode->label();
268 $form_mode_label = $this->t('Default');
270 $summary[] = t('Form mode: @mode', ['@mode' => $form_mode_label]);
271 if ($this->getSetting('override_labels')) {
272 $summary[] = $this->t(
273 'Overriden labels are used: %singular and %plural',
274 ['%singular' => $this->getSetting('label_singular'), '%plural' => $this->getSetting('label_plural')]
278 $summary[] = $this->t('Default labels are used.');
281 if ($this->getSetting('collapsible')) {
282 $summary[] = $this->t($this->getSetting('collapsed') ? 'Collapsible, collapsed by default' : 'Collapsible');
289 * Gets the entity type managed by this handler.
291 * @return \Drupal\Core\Entity\EntityTypeInterface
294 protected function getEntityTypeLabels() {
295 // The admin has specified the exact labels that should be used.
296 if ($this->getSetting('override_labels')) {
298 'singular' => $this->getSetting('label_singular'),
299 'plural' => $this->getSetting('label_plural'),
303 $this->createInlineFormHandler();
304 return $this->inlineFormHandler->getEntityTypeLabels();
309 * Checks whether we can build entity form at all.
311 * - Is IEF handler loaded?
312 * - Are we on a "real" entity form and not on default value widget?
314 * @param \Drupal\Core\Form\FormStateInterface $form_state
318 * TRUE if we are able to proceed with form build and FALSE if not.
320 protected function canBuildForm(FormStateInterface $form_state) {
321 if ($this->isDefaultValueWidget($form_state)) {
325 if (!$this->inlineFormHandler) {
333 * Prepares the form state for the current widget.
335 * @param \Drupal\Core\Form\FormStateInterface $form_state
337 * @param \Drupal\Core\Field\FieldItemListInterface $items
339 * @param bool $translating
340 * Whether there's a translation in progress.
342 protected function prepareFormState(FormStateInterface $form_state, FieldItemListInterface $items, $translating = FALSE) {
343 $widget_state = $form_state->get(['inline_entity_form', $this->iefId]);
344 if (empty($widget_state)) {
346 'instance' => $this->fieldDefinition,
351 // Store the $items entities in the widget state, for further manipulation.
352 foreach ($items as $delta => $item) {
353 $entity = $item->entity;
354 // The $entity can be NULL if the reference is broken.
356 // Display the entity in the correct translation.
358 $entity = TranslationHelper::prepareEntity($entity, $form_state);
360 $widget_state['entities'][$delta] = [
364 'needs_save' => $entity->isNew(),
368 $form_state->set(['inline_entity_form', $this->iefId], $widget_state);
373 * Gets inline entity form element.
375 * @param string $operation
376 * The operation (i.e. 'add' or 'edit').
377 * @param string $bundle
379 * @param string $langcode
381 * @param array $parents
382 * Array of parent element names.
383 * @param \Drupal\Core\Entity\EntityInterface $entity
384 * Optional entity object.
387 * IEF form element structure.
389 protected function getInlineEntityForm($operation, $bundle, $langcode, $delta, array $parents, EntityInterface $entity = NULL) {
391 '#type' => 'inline_entity_form',
392 '#entity_type' => $this->getFieldSetting('target_type'),
393 '#bundle' => $bundle,
394 '#langcode' => $langcode,
395 '#default_value' => $entity,
397 '#form_mode' => $this->getSetting('form_mode'),
398 '#save_entity' => FALSE,
399 '#ief_row_delta' => $delta,
400 // Used by Field API and controller methods to find the relevant
401 // values in $form_state.
402 '#parents' => $parents,
403 // Labels could be overridden in field widget settings. We won't have
404 // access to those in static callbacks (#process, ...) so let's add
406 '#ief_labels' => $this->getEntityTypeLabels(),
407 // Identifies the IEF widget to which the form belongs.
408 '#ief_id' => $this->getIefId(),
415 * Determines whether there's a translation in progress.
417 * Ensures that at least one target bundle has translations enabled.
418 * Otherwise the widget will skip translation even if it's happening
419 * on the parent form itself.
421 * @param \Drupal\Core\Form\FormStateInterface $form_state
425 * TRUE if translating is in progress, FALSE otherwise.
427 * @see \Drupal\inline_entity_form\TranslationHelper::initFormLangcodes()
429 protected function isTranslating(FormStateInterface $form_state) {
430 if (TranslationHelper::isTranslating($form_state)) {
431 $translation_manager = \Drupal::service('content_translation.manager');
432 $target_type = $this->getFieldSetting('target_type');
433 foreach ($this->getTargetBundles() as $bundle) {
434 if ($translation_manager->isEnabled($target_type, $bundle)) {
444 * After-build callback for removing the translatability clue from the widget.
446 * IEF expects the entity reference field to not be translatable, to avoid
447 * different translations having different references.
448 * However, that causes ContentTranslationHandler::addTranslatabilityClue()
449 * to add an "(all languages)" suffix to the widget title. That suffix is
450 * incorrect, since IEF does ensure that specific entity translations are
453 public static function removeTranslatabilityClue(array $element, FormStateInterface $form_state) {
454 $element['#title'] = $element['#field_title'];
459 * Adds submit callbacks to the inline entity form.
461 * @param array $element
462 * Form array structure.
464 public static function addIefSubmitCallbacks($element) {
465 $element['#ief_element_submit'][] = [get_called_class(), 'submitSaveEntity'];
470 * Marks created/edited entity with "needs save" flag.
472 * Note that at this point the entity is not yet saved, since the user might
473 * still decide to cancel the parent form.
475 * @param $entity_form
476 * The form of the entity being managed inline.
477 * @param \Drupal\Core\Form\FormStateInterface $form_state
478 * The form state of the parent form.
480 public static function submitSaveEntity($entity_form, FormStateInterface $form_state) {
481 $ief_id = $entity_form['#ief_id'];
482 /** @var \Drupal\Core\Entity\EntityInterface $entity */
483 $entity = $entity_form['#entity'];
485 if (in_array($entity_form['#op'], ['add', 'duplicate'])) {
486 // Determine the correct weight of the new element.
488 $entities = $form_state->get(['inline_entity_form', $ief_id, 'entities']);
489 if (!empty($entities)) {
490 $weight = max(array_keys($entities)) + 1;
492 // Add the entity to form state, mark it for saving, and close the form.
497 'needs_save' => TRUE,
499 $form_state->set(['inline_entity_form', $ief_id, 'entities'], $entities);
502 $delta = $entity_form['#ief_row_delta'];
503 $entities = $form_state->get(['inline_entity_form', $ief_id, 'entities']);
504 $entities[$delta]['entity'] = $entity;
505 $entities[$delta]['needs_save'] = TRUE;
506 $form_state->set(['inline_entity_form', $ief_id, 'entities'], $entities);
513 public function calculateDependencies() {
514 $dependencies = parent::calculateDependencies();
515 if ($entity_form_mode = $this->getEntityFormMode()) {
516 $dependencies['config'][] = $entity_form_mode->getConfigDependencyName();
518 return $dependencies;
522 * Gets the entity form mode instance for this widget.
524 * @return \Drupal\Core\Entity\EntityFormModeInterface|null
525 * The form mode instance, or NULL if the default one is used.
527 protected function getEntityFormMode() {
528 $form_mode = $this->getSetting('form_mode');
529 if ($form_mode != 'default') {
530 $entity_type_id = $this->getFieldSetting('target_type');
531 return $this->entityTypeManager->getStorage('entity_form_mode')->load($entity_type_id . '.' . $form_mode);
537 * Gets the access handler for the target entity type.
539 * @return \Drupal\Core\Entity\EntityAccessControlHandlerInterface
540 * The access handler.
542 protected function getAccessHandler() {
543 $entity_type_id = $this->getFieldSetting('target_type');
544 return $this->entityTypeManager->getAccessControlHandler($entity_type_id);
550 public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
551 if ($this->canBuildForm($form_state)) {
552 return parent::form($items, $form, $form_state, $get_delta);
558 * Determines if the current user can add any new entities.
562 protected function canAddNew() {
563 $create_bundles = $this->getCreateBundles();
564 return !empty($create_bundles);