rng-3.x-dev/src/Element/Registrants.php

src/Element/Registrants.php
<?php

namespace Drupal\rng\Element;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\rng\Form\RegistrantFields;
use Drupal\user\Entity\User;
use Drupal\rng\RegistrantsElementUtility as RegistrantsElement;

/**
 * Provides a form element for a registrant and person association.
 *
 * Properties:
 * - #event: The associated event entity.
 *
 * Usage example:
 * @code
 * $form['registrants'] = [
 *   '#type' => 'registrants',
 *   '#event' => $event_entity,
 *   '#registration' => $registration,
 * ];
 * @endcode
 *
 * @FormElement("registrants")
 */
class Registrants extends FormElement {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#input' => TRUE,
      '#process' => [
        [$class, 'processIdentityElement'],
      ],
      '#element_validate' => [
        [$class, 'validateIdentityElement'],
        [$class, 'validateRegisterable'],
        [$class, 'validateRegistrantCount'],
        ['\Drupal\rng\Form\RegistrantFields', 'validateForm'],
      ],
      '#pre_render' => [
        [$class, 'preRenderRegistrants'],
      ],
      // Required.
      '#event' => NULL,
      '#registration' => NULL,
      '#attached' => [
        'library' => ['rng/rng.elements.registrants'],
      ],
      // Use container so classes are applied.
      '#theme_wrappers' => ['container'],
      // Allow creation of which entity types + bundles:
      // Array of bundles keyed by entity type.
      '#allow_creation' => [],
      // Allow referencing existing entity types + bundles:
      // Array of bundles keyed by entity type.
      '#allow_reference' => [],
      // Minimum number of registrants (integer), or NULL for no minimum.
      // DEPRECATED - DETERMINED BY REGISTRATION OBJECT.
      '#registrants_minimum' => NULL,
      // Maximum number of registrants (integer), or NULL for no maximum.
      // DEPRECATED - DETERMINED BY REGISTRATION OBJECT.
      '#registrants_maximum' => NULL,
      // Get form display modes used when creating entities inline.
      // An array in the format: [entity_type][bundle] = form_mode_id.
      '#form_modes' => [],
    ];
  }

  /**
   * Process the registrant element.
   *
   * @param array $element
   *   An associative array containing the form structure of the element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   An associative array containing the structure of the form.
   *
   * @return array
   *   The new form structure for the element.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityMalformedException
   */
  public static function processIdentityElement(array &$element, FormStateInterface $form_state, array &$complete_form) {
    if (!isset($element['#event'])) {
      throw new \InvalidArgumentException('Element is missing #event property.');
    }
    if (!$element['#event'] instanceof EntityInterface) {
      throw new \InvalidArgumentException('#event for element is not an entity.');
    }

    /** @var \Drupal\rng\Entity\RegistrationInterface $registration */
    $registration = $element['#registration'];
    $event_meta = $registration->getEventMeta();
    $event_type = $event_meta->getEventType();
    $allow_anon = $event_type->getAllowAnonRegistrants();
    if (!$allow_anon && empty($element['#allow_creation']) && empty($element['#allow_reference'])) {
      throw new \InvalidArgumentException('Element cannot create or reference any entities.');
    }

    // Supporting services.
    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository */
    $entity_display_repository = \Drupal::service('entity_display.repository');
    /** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info */
    $bundle_info = \Drupal::service('entity_type.bundle.info');
    /** @var \Drupal\Core\Entity\EntityFormBuilder $entity_form_builder */
    $entity_form_builder = \Drupal::service('entity.form_builder');

    $parents = $element['#parents'];

    $event = $element['#event'];

    $ajax_wrapper_id_root = 'ajax-wrapper-' . implode('-', $parents);

    $element['#tree'] = TRUE;
    $element['#identity_element_root'] = TRUE;
    $element['#prefix'] = '<div id="' . $ajax_wrapper_id_root . '">';
    $element['#suffix'] = '</div>';

    /** @var \Drupal\rng\Entity\RegistrantInterface[] $people */
    $people = $element['#value'];

    $ajax_wrapper_id_people = 'ajax-wrapper-people-' . implode('-', $parents);

    $element['people'] = [
      '#prefix' => '<div id="' . $ajax_wrapper_id_people . '">',
      '#suffix' => '</div>',
      '#tree' => TRUE,
    ];

    $counter = 0;
    foreach ($people as $reg_id => $registrant) {
      $counter++;
      $curr_parent = array_merge($parents, [$counter]);
      /** @var \Drupal\rng\Form\RegistrantFields $helper */
      $reg_form = [
        '#parents' => $curr_parent,
        '#reg_counter' => $counter,
        '#reg_id' => $reg_id,
      ];
      $helper = new RegistrantFields($reg_form, $form_state, $registrant);

      $reg_form = $helper->getFields($reg_form, $form_state, $registrant);
      $row = [
        '#type' => 'fieldset',
        '#title' => 'Attendee ' . $counter . ' - ' . '<a href="/user">' . $registrant->label() . '</a>',
        '#open' => TRUE,
        '#parents' => $curr_parent,
        'registrant' => $reg_form,
        '#wrapper_attributes' => [
          'class' => ['registrant-grid'],
        ],
      ];
      $row['registrant']['#attributes']['class'][] = 'registrant-grid';
      $element['people'][] = $row;
    }

    if ($registration->canAddRegistrants()) {
      $person_subform = &$element['entities']['person'];

      $person_subform['new_person'] = [
        '#type' => 'details',
        '#open' => TRUE,
        '#tree' => TRUE,
        '#title' => t('New @entity_type', ['@entity_type' => 'Registrant']),
        '#identity_element_create_container' => TRUE,
      ];

      if (count($people)) {
        // Add New button.
        $person_subform['new_person']['load_create_form'] = [
          '#type' => 'submit',
          '#value' => t('Create new @label', ['@label' => $bundle_info->getBundleInfo('registrant')[$event_type->getDefaultRegistrantType()]['label']]),
          '#ajax' => [
            'callback' => [static::class, 'ajaxElementRoot'],
            'wrapper' => $ajax_wrapper_id_root,
          ],
          '#validate' => [
            [static::class, 'decoyValidator'],
          ],
          '#submit' => [
            [static::class, 'submitToggleCreateEntity'],
          ],
          '#toggle_create_entity' => TRUE,
          '#limit_validation_errors' => [],
        ];

      }
      else {
        // Set form.
        $person_subform['new_person']['newentityform'] = [
          '#tree' => TRUE,
          '#parents' => array_merge($parents,
            ['entities', 'person', 'new_person', 'newentityform']),
        ];

        /** @var \Drupal\rng\RegistrantFactoryInterface $registrant_factory */
        $registrant_factory = \Drupal::service('rng.registrant_factory');
        $new_person = $registrant_factory->createRegistrant([
          'event' => $event,
        ]);
        $new_person
          ->setRegistration($registration);
        $display = $entity_display_repository->getFormDisplay('registrant', $new_person->bundle());
        $display->buildForm($new_person, $person_subform['new_person']['newentityform'], $form_state);

        $person_subform['new_person']['actions'] = [
          '#type' => 'actions',
          '#weight' => 10000,
        ];
        $person_subform['new_person']['actions']['create'] = [
          '#type' => 'submit',
          '#value' => t('Create and add to registration'),
          '#ajax' => [
            'callback' => [static::class, 'ajaxElementRoot'],
            'wrapper' => $ajax_wrapper_id_root,
          ],
          '#limit_validation_errors' => [
            array_merge($parents, ['entities', 'person', 'registrant']),
            array_merge($parents, ['entities', 'person', 'new_person']),
          ],
          '#validate' => [
            [static::class, 'validateCreate'],
          ],
          '#submit' => [
            [static::class, 'submitCreate'],
          ],
        ];

        $person_subform['new_person']['actions']['cancel'] = [
          '#type' => 'submit',
          '#value' => t('Cancel'),
          '#ajax' => [
            'callback' => [static::class, 'ajaxElementRoot'],
            'wrapper' => $ajax_wrapper_id_root,
          ],
          '#limit_validation_errors' => [],
          '#toggle_create_entity' => FALSE,
          '#validate' => [
            [static::class, 'decoyValidator'],
          ],
          '#submit' => [
            [static::class, 'submitToggleCreateEntity'],
          ],
        ];

      }
    }

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
    $parents = $element['#parents'];
    $value = $form_state->get($parents);

    if ($value === NULL) {
      return $element['#default_value'] ?? [];
    }

    return $value;
  }

  /**
   * An empty form validator.
   *
   * This validator is used to prevent top level form validators from running.
   * Submission elements must have a dummy validator, not just an empty
   * #validate property.
   *
   * See \Drupal\Core\Form\FormValidator::executeValidateHandlers for the
   * critical core operation details.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function decoyValidator(array &$form, FormStateInterface $form_state) {
  }

  /**
   * Generic validator for the element.
   */
  public static function validateIdentityElement(&$element, FormStateInterface $form_state, &$complete_form) {
    $utility = new RegistrantsElement($element, $form_state);

    $registrants = $element['#value'];

    // Store original form submission in temporary values.
    $values = $form_state->getValue($element['#parents']);
    $form_state->setTemporaryValue(array_merge(['_registrants_values'], $element['#parents']), $values);

    // Change element value to registrant entities.
    $form_state->setValueForElement($element, $registrants);
  }

  /**
   * Validate whether all existing registrants are register-able.
   *
   * An identity may have been registered by another registration while
   * it is also stored in the state of another registration.
   */
  public static function validateRegisterable(&$element, FormStateInterface $form_state, &$complete_form) {
    $utility = new RegistrantsElement($element, $form_state);

    // Add existing registrants to whitelist.
    foreach ($element['#default_value'] as $existing_registrant) {
      $identity = $existing_registrant->getIdentity();
      if ($identity) {
        $utility->addWhitelistExisting($identity);
      }
    }

    /** @var \Drupal\rng\Entity\RegistrantInterface[] $registrants */
    $registrants = $element['#value'];
    $whitelisted = $utility->getWhitelistExisting();

    $identities = [];
    foreach ($registrants as $registrant) {
      $identity = $registrant->getIdentity();
      if ($identity) {
        $entity_type = $identity->getEntityTypeId();
        $id = $identity->id();
        // Check if identity can skip existing revalidation. This needs to be
        // done when the identity was created by this element.
        if (!isset($whitelisted[$entity_type][$id])) {
          $identities[$entity_type][$id] = $identity->label();
        }
      }
    }

    /** @var \Drupal\rng\EventManagerInterface $event_manager */
    $event_manager = \Drupal::service('rng.event_manager');
    $event = $element['#event'];
    $event_meta = $event_manager->getMeta($event);
    foreach ($identities as $entity_type => $identity_labels) {
      $registerable = $event_meta->identitiesCanRegister($entity_type, array_keys($identity_labels));
      // Flip identity entity IDs to array keys.
      $registerable = array_flip($registerable);
      foreach (array_diff_key($identities[$entity_type], $registerable) as $id => $label) {
        $form_state->setError($element, t('%name cannot register for this event.', [
          '%name' => $label,
        ]));
      }
    }
  }

  /**
   * Validate whether there are sufficient quantity of registrants.
   */
  public static function validateRegistrantCount(&$element, FormStateInterface $form_state, &$complete_form) {
    /** @var \Drupal\rng\Entity\RegistrantInterface[] $registrants */
    $registrants = $element['#value'];
    $count = count($registrants);

    if (isset($element['#registrants_minimum'])) {
      $minimum = $element['#registrants_minimum'];
      if ($count < $minimum) {
        $form_state->setError($element, t('There are not enough registrants on this registration. There must be at least @minimum registrants.', [
          '@minimum' => $minimum,
        ]));
      }
    }

    if (isset($element['#registrants_maximum'])) {
      $maximum = $element['#registrants_maximum'];
      if ($count > $maximum) {
        $form_state->setError($element, t('There are too many registrants on this registration. There must be at most @maximum registrants.', [
          '@maximum' => $maximum,
        ]));
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function preRenderRegistrants($element) {
    $element['#attributes']['class'][] = 'registrants-element';
    return $element;
  }

  /**
   * Ajax callback to return the entire element.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The entire element sub-form.
   */
  public static function ajaxElementRoot(array $form, FormStateInterface $form_state) {
    return RegistrantsElement::findElement($form, $form_state);
  }

  /**
   * Validate adding myself sub-form.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function validateMyself(array &$form, FormStateInterface $form_state) {
    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);
    $utility->buildRegistrant(TRUE);
  }

  /**
   * Validate adding existing entity sub-form.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public static function validateExisting(array &$form, FormStateInterface $form_state) {
    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);

    $utility->buildRegistrant(TRUE);

    $autocomplete_tree = array_merge($element['#parents'],
      ['entities', 'person', 'existing', 'existing_autocomplete']);

    $element_existing = NestedArray::getValue($element,
      ['entities', 'person', 'existing', 'existing_autocomplete']);
    $existing_entity_type = $element_existing['#target_type'];
    $existing_value = NestedArray::getValue($form_state->getTemporaryValue('_registrants_values'), $autocomplete_tree);

    if (!empty($existing_value)) {
      $identity = \Drupal::entityTypeManager()->getStorage($existing_entity_type)
        ->load($existing_value);
      if ($utility->identityExists($identity)) {
        $form_state->setError(NestedArray::getValue($form, $autocomplete_tree), t('Person is already on this registration.'));
      }
    }
    else {
      $form_state->setError(NestedArray::getValue($form, $autocomplete_tree), t('Choose a person.'));
    }
  }

  /**
   * Validate identity creation sub-form.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function validateCreate(array &$form, FormStateInterface $form_state) {
    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);

    $utility->buildRegistrant(TRUE);

    $new_person_tree = array_merge($element['#parents'],
      ['entities', 'person', 'new_person', 'newentityform']);
    $subform_newentity = NestedArray::getValue($form, $new_person_tree);

    $value = $form_state->getTemporaryValue(array_merge(['_registrants_values'], $element['#parents']));
    $form_state->setValue($element['#parents'], $value);

    $new_person = $form_state->get('newentity__entity');
    $form_display = $form_state->get('newentity__form_display');
    $form_display->extractFormValues($new_person, $subform_newentity, $form_state);
    $form_display->validateFormValues($new_person, $subform_newentity, $form_state);

    /** @var \Symfony\Component\Validator\ConstraintViolationListInterface $violations */
    $violations = $new_person->validate();
    if ($violations->count() == 0) {
      $form_state->set('newentity__entity', $new_person);
    }
    else {
      $triggering_element = $form_state->getTriggeringElement();
      foreach ($violations as $violation) {
        $form_state->setError($triggering_element, (string) $violation->getMessage());
      }
    }
  }

  /**
   * Submission callback to change the registrant from the default people.
   *
   * @param array $form
   *   The current form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function submitChangeDefault(array $form, FormStateInterface $form_state) {
    $form_state->setRebuild();

    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);

    $utility->setChangeIt(TRUE);

  }

  /**
   * Submission callback to close the selection interface.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function submitClose(array $form, FormStateInterface $form_state) {
    $form_state->setRebuild();

    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);

    $utility->setChangeIt(FALSE);
    $utility->clearPeopleFormInput();
  }

  /**
   * Submission callback for referencing the current user.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function submitMyself(array $form, FormStateInterface $form_state) {
    $form_state->setRebuild();

    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);

    $registrant = $utility->buildRegistrant();
    $utility->clearPeopleFormInput();

    $current_user = \Drupal::currentUser();
    if ($current_user->isAuthenticated()) {
      $person = User::load($current_user->id());
      $registrant->setIdentity($person);
    }
  }

  /**
   * Submission callback for existing entities.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function submitExisting(array $form, FormStateInterface $form_state) {
    $form_state->setRebuild();

    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);

    $registrant = $utility->buildRegistrant();
    $utility->clearPeopleFormInput();

    $autocomplete_tree = array_merge($element['#parents'],
      ['entities', 'person', 'existing', 'existing_autocomplete']);
    $existing_value = NestedArray::getValue($form_state->getTemporaryValue('_registrants_values'), $autocomplete_tree);

    $subform_autocomplete = NestedArray::getValue($form, $autocomplete_tree);
    $existing_entity_type = $subform_autocomplete['#target_type'];
    $person = \Drupal::entityTypeManager()->getStorage($existing_entity_type)
      ->load($existing_value);
    $registrant->setIdentity($person);

  }

  /**
   * Submission callback for creating new entities.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public static function submitCreate(array $form, FormStateInterface $form_state) {
    $form_state->setRebuild();

    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);

    // New entity.
    $new_entity_tree = array_merge($element['#parents'],
      ['entities', 'person', 'new_person', 'newentityform']);
    $subform_new_entity = NestedArray::getValue($form, $new_entity_tree);

    // Save the entity.
    /** @var \Drupal\Core\Entity\EntityInterface $new_person */
    $new_person = $form_state->get('newentity__entity');
    $display = $form_state->get('newentity__form_display');

    $value = $form_state->getTemporaryValue(array_merge(['_registrants_values'], $element['#parents']));
    $form_state->setValue($element['#parents'], $value);
    $display->extractFormValues($new_person, $subform_new_entity, $form_state);
    $new_person->save();
    $utility->addWhitelistExisting($new_person);

    $registrant = $utility->buildRegistrant();
    $utility->clearPeopleFormInput();

    $registrant->setIdentity($new_person);

  }

  /**
   * Submission callback for toggling the create sub-form.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function submitToggleCreateEntity(array $form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $form_state->setRebuild();

    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);
    $utility->setShowCreateEntitySubform($trigger['#toggle_create_entity']);
  }

  /**
   * Submission callback for removing a registrant.
   *
   * @param array $form
   *   The complete form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function submitRemovePerson(array $form, FormStateInterface $form_state) {
    $form_state->setRebuild();

    $element = RegistrantsElement::findElement($form, $form_state);
    $utility = new RegistrantsElement($element, $form_state);

    $trigger = $form_state->getTriggeringElement();
    $row = $trigger['#identity_element_registrant_row'];

    $registrants = $utility->getRegistrants();
    unset($registrants[$row]);
    $utility->setRegistrants($registrants);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc