diff --git a/config/sync/field.storage.node.field_samples.yml b/config/sync/field.storage.node.field_samples.yml index 742ea3fc..f5408646 100644 --- a/config/sync/field.storage.node.field_samples.yml +++ b/config/sync/field.storage.node.field_samples.yml @@ -16,7 +16,7 @@ type: materio_samples_field settings: { } module: materio_samples locked: false -cardinality: 4 +cardinality: -1 translatable: true indexes: { } persist_with_no_fields: false diff --git a/web/modules/custom/materio_samples/src/Plugin/Field/FieldWidget/SamplesDefaultWidget.php b/web/modules/custom/materio_samples/src/Plugin/Field/FieldWidget/SamplesDefaultWidget.php index 9d81f3c6..be1bed65 100644 --- a/web/modules/custom/materio_samples/src/Plugin/Field/FieldWidget/SamplesDefaultWidget.php +++ b/web/modules/custom/materio_samples/src/Plugin/Field/FieldWidget/SamplesDefaultWidget.php @@ -5,7 +5,7 @@ namespace Drupal\materio_samples\Plugin\Field\FieldWidget; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\taxonomy\Entity\Term; +use Drupal\user\Entity\User; use Drupal\workflow\Entity\WorkflowManager; /** @@ -23,130 +23,148 @@ use Drupal\workflow\Entity\WorkflowManager; class SamplesDefaultWidget extends WidgetBase { /** - * {@inheritdoc} + * Loads the showroom terms in a deterministic order. + * + * A stable secondary sort on tid avoids the non-deterministic ordering of + * terms sharing the same weight. + * + * @return \Drupal\taxonomy\Entity\Term[] + * The showroom terms keyed by term id. */ - public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) { - // construct "manually" the list of items + protected function getShowroomTerms() { $vid = $this->fieldDefinition->getSetting('vid'); - $query = \Drupal::entityQuery('taxonomy_term') - ->sort('weight', 'DESC') - // ->sort('tid', 'DESC') - ->accessCheck() - ->condition('vid', $vid); - $tids = $query->execute(); - $terms = Term::loadMultiple($tids); + $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $tids = $storage->getQuery() + ->condition('vid', $vid) + ->sort('weight', 'DESC') + ->sort('tid', 'ASC') + ->accessCheck(FALSE) + ->execute(); + return $storage->loadMultiple($tids); + } - // define the cardinality, this will remove the add_more btn - $this->fieldDefinition->getFieldStorageDefinition()->setCardinality(count($terms)); - - $locations = []; - foreach ($items as $delta => $item) { - $locations[$item->target_id] = $item->location; - } - - $delta = 0; - foreach ($terms as $term) { - // remove masqué - $sid = WorkflowManager::getCurrentStateId($term, 'field_workflow'); - if($sid == 'workflow_hidden') continue; - $location = isset($locations[$term->id()]) ? $locations[$term->id()] : ''; - $value = array( - 'location' => $location, - 'target_id'=> $term->id() - ); - $items->set($delta, $value); - $delta ++; - } - - // then call the normal form - $elements = parent::form($items, $form, $form_state, $get_delta); - // dsm($elements); - - // Arrange the form object to remove draggable table stuff - $elements['widget']['#cardinality_multiple'] = FALSE; - for ($i=0; $i <= $delta ; $i++) { - if(isset($elements['widget'][$i]['_weight'])){ - $elements['widget'][$i]['_weight']['#type'] = 'hidden'; + /** + * Returns the showroom ids owned by the current user (field_showroom). + * + * @return array + * An array keyed by showroom term id. + */ + protected function getOwnedShowroomIds() { + $owned = []; + $user = User::load(\Drupal::currentUser()->id()); + if ($user) { + foreach ($user->get('field_showroom')->referencedEntities() as $showroom) { + $owned[$showroom->id()] = TRUE; } } + return $owned; + } + + /** + * {@inheritdoc} + * + * Renders exactly one row per showroom term. The identity of each value is + * its target_id: we never reindex by delta nor mutate $items, and we do not + * override the storage cardinality. + */ + protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) { + // Map existing values by showroom id (identity = target_id, not delta). + $locations = []; + foreach ($items as $item) { + if (!$item->isEmpty() && $item->target_id) { + $locations[$item->target_id] = $item->location; + } + } + + $language = \Drupal::languageManager()->getCurrentLanguage()->getId(); + $entity_repository = \Drupal::service('entity.repository'); + $can_edit_any = \Drupal::currentUser()->hasPermission('materio_samples_edit_any_sample'); + $owned = $this->getOwnedShowroomIds(); + + $elements = []; + $delta = 0; + foreach ($this->getShowroomTerms() as $term) { + $tid = $term->id(); + $is_hidden = WorkflowManager::getCurrentStateId($term, 'field_workflow') === 'workflow_hidden'; + $location = $locations[$tid] ?? ''; + + // Preserve values of workflow-hidden showrooms so a save never drops + // them, but do not display an editable row. + if ($is_hidden) { + if ($location !== '') { + $elements[$delta] = [ + 'target_id' => ['#type' => 'value', '#value' => $tid], + 'location' => ['#type' => 'value', '#value' => $location], + ]; + $delta++; + } + continue; + } + + $term_trans = $entity_repository->getTranslationFromContext($term, $language); + $disabled = !($can_edit_any || isset($owned[$tid])); + + $elements[$delta] = [ + // target_id is a non-editable value: it always round-trips and cannot + // be tampered with, keeping location <-> showroom paired. + 'target_id' => ['#type' => 'value', '#value' => $tid], + 'location' => [ + '#type' => 'textfield', + '#title' => $term_trans->getName(), + '#default_value' => $location, + '#size' => 10, + '#maxlength' => 15, + '#disabled' => $disabled, + '#attributes' => ['class' => ['container-inline']], + ], + ]; + $delta++; + } + + // #cardinality_multiple = FALSE => the field_multiple_value_form theme + // renders each row as-is, with no draggable table, no weight, no + // add-more/remove buttons. + $elements += [ + '#theme' => 'field_multiple_value_form', + '#field_name' => $this->fieldDefinition->getName(), + '#cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), + '#cardinality_multiple' => FALSE, + '#required' => $this->fieldDefinition->isRequired(), + '#title' => $this->fieldDefinition->getLabel(), + '#description' => $this->getFilteredDescription(), + '#max_delta' => $delta > 0 ? $delta - 1 : 0, + ]; return $elements; } /** * {@inheritdoc} + * + * Required by WidgetBase but unused: the whole widget is built in + * formMultipleElements(). */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - $language = \Drupal::languageManager()->getCurrentLanguage()->getId(); - - // get default values - $target_id = isset($items[$delta]->target_id) ? $items[$delta]->target_id : 0; - $location = isset($items[$delta]->location) ? $items[$delta]->location : ''; - // dsm($target_id); - - // return nothing if target_id is null - if(!$target_id) return; - - $term = Term::load($target_id); - - // translate the term - $term = \Drupal::service('entity.repository')->getTranslationFromContext($term, $language); - - // dsm($element); - // $element['#attributes'] = array('class' => array('container-inline')); - // $element['container'] = [ - // '#type' => 'container', - // '#field_prefix' => '