public function ParagraphsWidget::formElement in Paragraphs 8
Uses a similar approach to populate a new translation.
Overrides WidgetInterface::formElement
See also
\Drupal\content_translation\Controller\ContentTranslationController::prepareTranslation()
File
- src/
Plugin/ Field/ FieldWidget/ ParagraphsWidget.php, line 357
Class
- ParagraphsWidget
- Plugin implementation of the 'entity_reference_revisions paragraphs' widget.
Namespace
Drupal\paragraphs\Plugin\Field\FieldWidgetCode
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$field_name = $this->fieldDefinition
->getName();
$parents = $element['#field_parents'];
/** @var \Drupal\paragraphs\Entity\Paragraph $paragraphs_entity */
$paragraphs_entity = NULL;
$host = $items
->getEntity();
$widget_state = static::getWidgetState($parents, $field_name, $form_state);
$entity_type_manager = \Drupal::entityTypeManager();
$target_type = $this
->getFieldSetting('target_type');
$item_mode = isset($widget_state['paragraphs'][$delta]['mode']) ? $widget_state['paragraphs'][$delta]['mode'] : 'edit';
$default_edit_mode = $this
->getSetting('edit_mode');
$closed_mode_setting = isset($widget_state['closed_mode']) ? $widget_state['closed_mode'] : $this
->getSetting('closed_mode');
$autocollapse_setting = isset($widget_state['autocollapse']) ? $widget_state['autocollapse'] : $this
->getSetting('autocollapse');
$show_must_be_saved_warning = !empty($widget_state['paragraphs'][$delta]['show_warning']);
if (isset($widget_state['paragraphs'][$delta]['entity'])) {
$paragraphs_entity = $widget_state['paragraphs'][$delta]['entity'];
}
elseif (isset($items[$delta]->entity)) {
$paragraphs_entity = $items[$delta]->entity;
// We don't have a widget state yet, get from selector settings.
if (!isset($widget_state['paragraphs'][$delta]['mode'])) {
if ($default_edit_mode == 'open' || $widget_state['items_count'] < $this
->getSetting('closed_mode_threshold')) {
$item_mode = 'edit';
}
elseif ($default_edit_mode == 'closed') {
$item_mode = 'closed';
}
elseif ($default_edit_mode == 'closed_expand_nested') {
$item_mode = 'closed';
$field_definitions = $paragraphs_entity
->getFieldDefinitions();
// If the paragraph contains other paragraphs, then open it.
foreach ($field_definitions as $field_definition) {
if ($field_definition
->getType() == 'entity_reference_revisions' && $field_definition
->getSetting('target_type') == 'paragraph') {
$item_mode = 'edit';
break;
}
}
}
}
}
elseif (isset($widget_state['selected_bundle'])) {
$entity_type = $entity_type_manager
->getDefinition($target_type);
$bundle_key = $entity_type
->getKey('bundle');
$paragraphs_entity = $entity_type_manager
->getStorage($target_type)
->create(array(
$bundle_key => $widget_state['selected_bundle'],
));
$paragraphs_entity
->setParentEntity($host, $field_name);
$item_mode = 'edit';
}
if ($paragraphs_entity) {
// Detect if we are translating.
$this
->initIsTranslating($form_state, $host);
$langcode = $form_state
->get('langcode');
if (!$this->isTranslating) {
// Set the langcode if we are not translating.
$langcode_key = $paragraphs_entity
->getEntityType()
->getKey('langcode');
if ($paragraphs_entity
->get($langcode_key)->value != $langcode) {
// If a translation in the given language already exists, switch to
// that. If there is none yet, update the language.
if ($paragraphs_entity
->hasTranslation($langcode)) {
$paragraphs_entity = $paragraphs_entity
->getTranslation($langcode);
}
else {
$paragraphs_entity
->set($langcode_key, $langcode);
}
}
}
else {
// If the node is being translated, the paragraphs should be all open
// when the form is not being rebuilt (E.g. when clicked on a paragraphs
// action) and when the translation is being added.
if (!$form_state
->isRebuilding() && $host
->getTranslationStatus($langcode) == TranslationStatusInterface::TRANSLATION_CREATED) {
$item_mode = 'edit';
}
// Add translation if missing for the target language.
if (!$paragraphs_entity
->hasTranslation($langcode)) {
// Get the selected translation of the paragraph entity.
$entity_langcode = $paragraphs_entity
->language()
->getId();
$source = $form_state
->get([
'content_translation',
'source',
]);
$source_langcode = $source ? $source
->getId() : $entity_langcode;
// Make sure the source language version is used if available. It is a
// the host and fetching the translation without this check could lead
// valid scenario to have no paragraphs items in the source version of
// to an exception.
if ($paragraphs_entity
->hasTranslation($source_langcode)) {
$paragraphs_entity = $paragraphs_entity
->getTranslation($source_langcode);
}
// The paragraphs entity has no content translation source field if
// no paragraph entity field is translatable, even if the host is.
if ($paragraphs_entity
->hasField('content_translation_source')) {
// Initialise the translation with source language values.
$paragraphs_entity
->addTranslation($langcode, $paragraphs_entity
->toArray());
$translation = $paragraphs_entity
->getTranslation($langcode);
$manager = \Drupal::service('content_translation.manager');
$manager
->getTranslationMetadata($translation)
->setSource($paragraphs_entity
->language()
->getId());
}
}
// If any paragraphs type is translatable do not switch.
if ($paragraphs_entity
->hasField('content_translation_source')) {
// Switch the paragraph to the translation.
$paragraphs_entity = $paragraphs_entity
->getTranslation($langcode);
}
}
// If untranslatable fields are hidden while translating, we are
// translating the parent and the Paragraph is open, then close the
// Paragraph if it does not have translatable fields.
$translating_force_close = FALSE;
if (\Drupal::moduleHandler()
->moduleExists('content_translation')) {
$manager = \Drupal::service('content_translation.manager');
$settings = $manager
->getBundleTranslationSettings('paragraph', $paragraphs_entity
->getParagraphType()
->id());
if (!empty($settings['untranslatable_fields_hide']) && $this->isTranslating) {
$translating_force_close = TRUE;
$display = EntityFormDisplay::collectRenderDisplay($paragraphs_entity, $this
->getSetting('form_display_mode'));
// Check if the paragraph has translatable fields.
foreach (array_keys($display
->get('content')) as $field) {
if ($paragraphs_entity
->hasField($field)) {
$field_definition = $paragraphs_entity
->get($field)
->getFieldDefinition();
// Check if we are referencing paragraphs.
$is_paragraph = $field_definition
->getType() == 'entity_reference_revisions' && $field_definition
->getSetting('target_type') == 'paragraph';
if ($is_paragraph || $field_definition
->isTranslatable()) {
$translating_force_close = FALSE;
break;
}
}
}
if ($translating_force_close) {
$item_mode = 'closed';
}
}
}
$element_parents = $parents;
$element_parents[] = $field_name;
$element_parents[] = $delta;
$element_parents[] = 'subform';
$id_prefix = implode('-', array_merge($parents, array(
$field_name,
$delta,
)));
$wrapper_id = Html::getUniqueId($id_prefix . '-item-wrapper');
$element += array(
'#type' => 'container',
'#element_validate' => array(
array(
$this,
'elementValidate',
),
),
'#paragraph_type' => $paragraphs_entity
->bundle(),
'subform' => array(
'#type' => 'container',
'#parents' => $element_parents,
),
);
$element['#prefix'] = '<div id="' . $wrapper_id . '">';
$element['#suffix'] = '</div>';
// Create top section structure with all needed subsections.
$element['top'] = [
'#type' => 'container',
'#weight' => -1000,
'#attributes' => [
'class' => [
'paragraph-top',
// Add a flag to indicate if the add_above feature is enabled and
// should be injected client-side.
$this
->isFeatureEnabled('add_above') ? 'add-above-on' : 'add-above-off',
],
],
// Section for paragraph type information.
'type' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'paragraph-type',
],
],
],
// Section for info icons.
'icons' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'paragraph-info',
],
],
],
'summary' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'paragraph-summary',
],
],
],
// Paragraphs actions element for actions and dropdown actions.
'actions' => [
'#type' => 'paragraphs_actions',
],
];
// Holds information items.
$info = [];
$item_bundles = \Drupal::service('entity_type.bundle.info')
->getBundleInfo($target_type);
if (isset($item_bundles[$paragraphs_entity
->bundle()])) {
$bundle_info = $item_bundles[$paragraphs_entity
->bundle()];
$element['top']['type']['label'] = [
'#markup' => $bundle_info['label'],
];
// Type icon and label bundle.
if ($icon_url = $paragraphs_entity
->getParagraphType()
->getIconUrl()) {
$element['top']['type']['icon'] = [
'#theme' => 'image',
'#uri' => $icon_url,
'#attributes' => [
'class' => [
'paragraph-type-icon',
],
'title' => $bundle_info['label'],
],
'#weight' => 0,
// We set inline height and width so icon don't resize on first load
// while CSS is still not loaded.
'#height' => 16,
'#width' => 16,
];
}
$element['top']['type']['label'] = [
'#markup' => '<span class="paragraph-type-label">' . $bundle_info['label'] . '</span>',
'#weight' => 1,
];
// Widget actions.
$widget_actions = [
'actions' => [],
'dropdown_actions' => [],
];
$widget_actions['dropdown_actions']['duplicate_button'] = [
'#type' => 'submit',
'#value' => $this
->t('Duplicate'),
'#name' => $id_prefix . '_duplicate',
'#weight' => 502,
'#submit' => [
[
get_class($this),
'duplicateSubmit',
],
],
'#limit_validation_errors' => [
array_merge($parents, [
$field_name,
$delta,
]),
],
'#delta' => $delta,
'#ajax' => [
'callback' => [
get_class($this),
'itemAjax',
],
'wrapper' => $widget_state['ajax_wrapper_id'],
],
'#access' => $this
->duplicateButtonAccess($paragraphs_entity),
'#attributes' => [
'class' => [
'button--small',
],
],
];
// Force the closed mode when the user cannot edit the Paragraph.
if (!$paragraphs_entity
->access('update')) {
$item_mode = 'closed';
}
if ($item_mode != 'remove') {
$widget_actions['dropdown_actions']['remove_button'] = [
'#type' => 'submit',
'#value' => $this
->t('Remove'),
'#name' => $id_prefix . '_remove',
'#weight' => 501,
'#submit' => [
[
get_class($this),
'paragraphsItemSubmit',
],
],
// Ignore all validation errors because deleting invalid paragraphs
// is allowed.
'#limit_validation_errors' => [],
'#delta' => $delta,
'#ajax' => [
'callback' => array(
get_class($this),
'itemAjax',
),
'wrapper' => $widget_state['ajax_wrapper_id'],
],
'#access' => $this
->removeButtonAccess($paragraphs_entity),
'#paragraphs_mode' => 'remove',
'#attributes' => [
'class' => [
'button--small',
],
],
];
}
if ($item_mode == 'edit') {
if (isset($paragraphs_entity)) {
$widget_actions['actions']['collapse_button'] = [
'#value' => $this
->t('Collapse'),
'#name' => $id_prefix . '_collapse',
'#weight' => 1,
'#submit' => [
[
get_class($this),
'paragraphsItemSubmit',
],
],
'#limit_validation_errors' => [
array_merge($parents, [
$field_name,
$delta,
]),
],
'#delta' => $delta,
'#ajax' => [
'callback' => [
get_class($this),
'itemAjax',
],
'wrapper' => $widget_state['ajax_wrapper_id'],
],
'#access' => $paragraphs_entity
->access('update') && !$translating_force_close,
'#paragraphs_mode' => 'closed',
'#paragraphs_show_warning' => TRUE,
'#attributes' => [
'class' => [
'paragraphs-icon-button',
'paragraphs-icon-button-collapse',
'button--extrasmall',
],
'title' => $this
->t('Collapse'),
],
];
}
}
else {
$widget_actions['actions']['edit_button'] = $this
->expandButton([
'#type' => 'submit',
'#value' => $this
->t('Edit'),
'#name' => $id_prefix . '_edit',
'#weight' => 1,
'#submit' => [
[
get_class($this),
'paragraphsItemSubmit',
],
],
'#limit_validation_errors' => [
array_merge($parents, [
$field_name,
$delta,
]),
],
'#delta' => $delta,
'#ajax' => [
'callback' => [
get_class($this),
'itemAjax',
],
'wrapper' => $widget_state['ajax_wrapper_id'],
],
'#access' => $paragraphs_entity
->access('update') && !$translating_force_close,
'#paragraphs_mode' => 'edit',
'#attributes' => [
'class' => [
'paragraphs-icon-button',
'paragraphs-icon-button-edit',
'button--extrasmall',
],
'title' => $this
->t('Edit'),
],
]);
if ($show_must_be_saved_warning && $paragraphs_entity
->isChanged()) {
$info['changed'] = [
'#theme' => 'paragraphs_info_icon',
'#message' => $this
->t('You have unsaved changes on this @title item.', [
'@title' => $this
->getSetting('title'),
]),
'#icon' => 'changed',
];
}
if (!$paragraphs_entity
->isPublished()) {
$info['preview'] = [
'#theme' => 'paragraphs_info_icon',
'#message' => $this
->t('Unpublished'),
'#icon' => 'view',
];
}
}
// If update is disabled we will show lock icon in actions section.
if (!$paragraphs_entity
->access('update')) {
$widget_actions['actions']['edit_disabled'] = [
'#theme' => 'paragraphs_info_icon',
'#message' => $this
->t('You are not allowed to edit or remove this @title.', [
'@title' => $this
->getSetting('title'),
]),
'#icon' => 'lock',
'#weight' => 1,
];
}
// Avoid checking delete access for new entities.
$delete_access = $paragraphs_entity
->isNew() || $paragraphs_entity
->access('delete');
if (!$paragraphs_entity
->access('update') && $delete_access) {
$info['edit'] = [
'#theme' => 'paragraphs_info_icon',
'#message' => $this
->t('You are not allowed to edit this @title.', [
'@title' => $this
->getSetting('title'),
]),
'#icon' => 'edit-disabled',
];
}
elseif (!$delete_access && $paragraphs_entity
->access('update')) {
$info['remove'] = [
'#theme' => 'paragraphs_info_icon',
'#message' => $this
->t('You are not allowed to remove this @title.', [
'@title' => $this
->getSetting('title'),
]),
'#icon' => 'delete-disabled',
];
}
$context = [
'form' => $form,
'widget' => self::getWidgetState($parents, $field_name, $form_state),
'items' => $items,
'delta' => $delta,
'element' => $element,
'form_state' => $form_state,
'paragraphs_entity' => $paragraphs_entity,
'is_translating' => $this->isTranslating,
'allow_reference_changes' => $this
->allowReferenceChanges(),
];
// Allow modules to alter widget actions.
\Drupal::moduleHandler()
->alter('paragraphs_widget_actions', $widget_actions, $context);
if (!empty($widget_actions['actions'])) {
// Expand all actions to proper submit elements and add it to top
// actions sub component.
$element['top']['actions']['actions'] = array_map([
$this,
'expandButton',
], $widget_actions['actions']);
}
if (!empty($widget_actions['dropdown_actions'])) {
// Expand all dropdown actions to proper submit elements and add
// them to top dropdown actions sub component.
$element['top']['actions']['dropdown_actions'] = array_map([
$this,
'expandButton',
], $widget_actions['dropdown_actions']);
}
}
$display = EntityFormDisplay::collectRenderDisplay($paragraphs_entity, $this
->getSetting('form_display_mode'));
// @todo Remove as part of https://www.drupal.org/node/2640056
if (\Drupal::moduleHandler()
->moduleExists('field_group')) {
$context = [
'entity_type' => $paragraphs_entity
->getEntityTypeId(),
'bundle' => $paragraphs_entity
->bundle(),
'entity' => $paragraphs_entity,
'context' => 'form',
'display_context' => 'form',
'mode' => $display
->getMode(),
];
field_group_attach_groups($element['subform'], $context);
if (method_exists(FormatterHelper::class, 'formProcess')) {
$element['subform']['#process'][] = [
FormatterHelper::class,
'formProcess',
];
}
elseif (function_exists('field_group_form_pre_render')) {
$element['subform']['#pre_render'][] = 'field_group_form_pre_render';
}
elseif (function_exists('field_group_form_process')) {
$element['subform']['#process'][] = 'field_group_form_process';
}
}
if ($item_mode == 'edit') {
$display
->buildForm($paragraphs_entity, $element['subform'], $form_state);
$hide_untranslatable_fields = $paragraphs_entity
->isDefaultTranslationAffectedOnly();
$summary = $paragraphs_entity
->getSummaryItems();
if (!empty($summary)) {
$element['top']['summary']['fields_info'] = [
'#theme' => 'paragraphs_summary',
'#summary' => $summary,
'#expanded' => TRUE,
'#access' => $paragraphs_entity
->access('update') || $paragraphs_entity
->access('view'),
];
}
$info = array_merge($info, $paragraphs_entity
->getIcons());
foreach (Element::children($element['subform']) as $field) {
if ($paragraphs_entity
->hasField($field)) {
$field_definition = $paragraphs_entity
->get($field)
->getFieldDefinition();
// Do a check if we have to add a class to the form element. We need
// those classes (paragraphs-content and paragraphs-behavior) to show
// and hide elements, depending of the active perspective.
// We need them to filter out entity reference revisions fields that
// reference paragraphs, cause otherwise we have problems with showing
// and hiding the right fields in nested paragraphs.
$is_paragraph_field = FALSE;
if ($field_definition
->getType() == 'entity_reference_revisions') {
// Check if we are referencing paragraphs.
if ($field_definition
->getSetting('target_type') == 'paragraph') {
$is_paragraph_field = TRUE;
}
}
if (!$is_paragraph_field) {
$element['subform'][$field]['#attributes']['class'][] = 'paragraphs-content';
$element['top']['summary']['fields_info'] = [
'#theme' => 'paragraphs_summary',
'#summary' => $summary,
'#expanded' => TRUE,
'#access' => $paragraphs_entity
->access('update') || $paragraphs_entity
->access('view'),
];
}
$translatable = $field_definition
->isTranslatable();
// Hide untranslatable fields when configured to do so except
// paragraph fields.
if (!$translatable && $this->isTranslating && !$is_paragraph_field) {
if ($hide_untranslatable_fields) {
$element['subform'][$field]['#access'] = FALSE;
}
else {
$element['subform'][$field]['widget']['#after_build'][] = [
static::class,
'addTranslatabilityClue',
];
}
}
}
}
// Build the behavior plugins fields, do not display behaviors when
// translating and untranslatable fields are hidden.
$paragraphs_type = $paragraphs_entity
->getParagraphType();
if ($paragraphs_type && \Drupal::currentUser()
->hasPermission('edit behavior plugin settings') && (!$this->isTranslating || !$hide_untranslatable_fields)) {
$element['behavior_plugins']['#weight'] = -99;
foreach ($paragraphs_type
->getEnabledBehaviorPlugins() as $plugin_id => $plugin) {
$element['behavior_plugins'][$plugin_id] = [
'#type' => 'container',
'#group' => implode('][', array_merge($element_parents, [
'paragraph_behavior',
])),
'#parents' => array_merge(array_slice($element_parents, 0, -1), [
'behavior_plugins',
$plugin_id,
]),
];
$subform_state = SubformState::createForSubform($element['behavior_plugins'][$plugin_id], $form, $form_state);
if ($plugin_form = $plugin
->buildBehaviorForm($paragraphs_entity, $element['behavior_plugins'][$plugin_id], $subform_state)) {
$element['behavior_plugins'][$plugin_id] = $plugin_form;
// Add the paragraphs-behavior class, so that we are able to show
// and hide behavior fields, depending on the active perspective.
$element['behavior_plugins'][$plugin_id]['#attributes']['class'][] = 'paragraphs-behavior';
}
}
}
}
elseif ($item_mode == 'closed') {
$element['subform'] = [];
$element['behavior_plugins'] = [];
if ($closed_mode_setting === 'preview') {
// The closed paragraph is displayed as a rendered preview.
$view_builder = $entity_type_manager
->getViewBuilder('paragraph');
$element['preview'] = $view_builder
->view($paragraphs_entity, 'preview', $paragraphs_entity
->language()
->getId());
$element['preview']['#access'] = $paragraphs_entity
->access('view');
}
else {
// The closed paragraph is displayed as a summary.
if ($paragraphs_entity) {
$summary = $paragraphs_entity
->getSummaryItems();
if (!empty($summary)) {
$element['top']['summary']['fields_info'] = [
'#theme' => 'paragraphs_summary',
'#summary' => $summary,
'#expanded' => FALSE,
'#access' => $paragraphs_entity
->access('update') || $paragraphs_entity
->access('view'),
];
}
$info = array_merge($info, $paragraphs_entity
->getIcons());
}
}
}
else {
$element['subform'] = array();
}
// If we have any info items lets add them to the top section.
if (!empty($info)) {
foreach ($info as $info_item) {
if (!isset($info_item['#access']) || $info_item['#access']) {
$element['top']['icons']['items'] = $info;
break;
}
}
}
$element['subform']['#attributes']['class'][] = 'paragraphs-subform';
$element['subform']['#access'] = $paragraphs_entity
->access('update');
if ($item_mode == 'remove') {
$element['#access'] = FALSE;
}
$widget_state['paragraphs'][$delta]['entity'] = $paragraphs_entity;
$widget_state['paragraphs'][$delta]['display'] = $display;
$widget_state['paragraphs'][$delta]['mode'] = $item_mode;
$widget_state['closed_mode'] = $closed_mode_setting;
$widget_state['autocollapse'] = $autocollapse_setting;
$widget_state['autocollapse_default'] = $this
->getSetting('autocollapse');
static::setWidgetState($parents, $field_name, $form_state, $widget_state);
}
else {
$element['#access'] = FALSE;
}
return $element;
}