acquia_commercemanager-8.x-1.122/modules/acm/src/Element/AcmAddress.php

modules/acm/src/Element/AcmAddress.php
<?php

namespace Drupal\acm\Element;

use Drupal\acm\Connector\RouteException;
use Drupal\address\LabelHelper;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
use Symfony\Component\HttpFoundation\Request;

/**
 * Provides an ACM Address form element.
 *
 * ACM Address form elements contain a group of sub-elements for each address
 * components.
 *
 * @FormElement("acm_address")
 */
class AcmAddress extends FormElement {

  /**
   * The default address values.
   *
   * @var array
   */
  private static $addressDefaults = [
    'telephone' => '',
    'address_id' => 0,
    'title' => '',
    'firstname' => '',
    'lastname' => '',
    'street' => '',
    'street2' => '',
    'city' => '',
    'region' => '',
    'postcode' => '',
    'country_id' => 'US',
    'default_billing' => 0,
    'default_shipping' => 0,
  ];

  /**
   * Calculate dynamic address parts per country.
   *
   * @param string $country
   *   Country code.
   *
   * @return array
   *   Calculated dynamic form parts.
   */
  public static function calculateDynamicParts($country) {
    $dynamic_parts = [];
    $addressFormatRepository = \Drupal::service('address.address_format_repository');
    $address_format = $addressFormatRepository->get($country);
    $subdivisionRepository = \Drupal::service('address.subdivision_repository');
    $options = $subdivisionRepository->getList([$country]);
    $labels = LabelHelper::getFieldLabels($address_format);

    // Update region options based on country.
    $dynamic_parts['region']['#options'] = $options;
    $dynamic_parts['region']['#required'] = TRUE;
    $dynamic_parts['region']['#access'] = TRUE;

    // Update labels.
    $cityLabel = $labels['locality'];
    $postcodeLabel = $labels['postalCode'];
    $regionLabel = $labels['administrativeArea'];
    $dynamic_parts['region']['#title'] = $regionLabel;
    $dynamic_parts['postcode']['#title'] = $postcodeLabel;
    $dynamic_parts['city']['#title'] = $cityLabel;

    if (empty($cityLabel)) {
      $dynamic_parts['city']['#required'] = FALSE;
      $dynamic_parts['city']['#access'] = FALSE;
    }

    if (empty($postcodeLabel)) {
      $dynamic_parts['postcode']['#required'] = FALSE;
      $dynamic_parts['postcode']['#access'] = FALSE;
    }

    if (empty($regionLabel) || empty($options)) {
      $dynamic_parts['region']['#required'] = FALSE;
      $dynamic_parts['region']['#access'] = FALSE;
    }

    return $dynamic_parts;
  }

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#input' => TRUE,
      '#markup' => '',
      '#process' => [
        [$class, 'processAcmAddress'],
      ],
      '#theme_wrappers' => ['form_element'],
      '#display_telephone' => FALSE,
      '#display_billing' => FALSE,
      '#display_shipping' => FALSE,
      '#display_title' => FALSE,
      '#display_firstname' => FALSE,
      '#display_lastname' => FALSE,
      '#include_address_id' => FALSE,
      '#validate_address' => FALSE,
      '#address_review_text' => '',
      '#address_failed_text' => '',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
    if ($input === FALSE || empty($input)) {
      $element += ['#default_value' => []];
      return $element['#default_value'];
    }

    $address_input = [];

    // Flatted the input.
    array_walk_recursive($input, function ($value, $key) use (&$address_input) {
      $address_input[$key] = $value;
    });

    // Throw out all invalid array keys.
    $value = [];
    foreach (static::$addressDefaults as $allowed_key => $default) {
      // These should be strings, but allow other scalars since they might be
      // valid input in programmatic form submissions. Any nested array values
      // are ignored.
      if (isset($address_input[$allowed_key]) && is_scalar($address_input[$allowed_key])) {
        $value[$allowed_key] = (string) $address_input[$allowed_key];
      }
    }

    return $value;
  }

  /**
   * Expand a acm_address field into multiple inputs.
   */
  public static function processAcmAddress(&$element, FormStateInterface $form_state, &$complete_form) {
    $default_address = (array) $element['#default_value'];
    $address = $default_address + static::$addressDefaults;
    $country = !empty($address['country_id']) ? $address['country_id'] : 'US';
    $countryRepository = \Drupal::service('address.country_repository');
    $subdivisionRepository = \Drupal::service('address.subdivision_repository');
    $addressFormatRepository = \Drupal::service('address.address_format_repository');
    $address_format = $addressFormatRepository->get($country);
    $labels = LabelHelper::getFieldLabels($address_format);

    if (!empty($element['#include_address_id'])) {
      $element['address_id'] = [
        '#type' => 'hidden',
        '#default_value' => $address['address_id'],
      ];
    }

    if (!empty($element['#display_title'])) {
      $element['title'] = [
        '#type' => 'acm_title_select',
        '#title' => t('Title'),
        '#default_value' => empty($address['title']) ? NULL : $address['title'],
        '#required' => TRUE,
        '#placeholder' => t('Title*'),
      ];
    }

    if (!empty($element['#display_firstname'])) {
      $element['firstname'] = [
        '#type' => 'textfield',
        '#title' => t('First Name'),
        '#default_value' => $address['firstname'],
        '#required' => TRUE,
        '#placeholder' => t('First Name*'),
      ];
    }

    if (!empty($element['#display_lastname'])) {
      $element['lastname'] = [
        '#type' => 'textfield',
        '#title' => t('Last Name'),
        '#default_value' => $address['lastname'],
        '#required' => TRUE,
        '#placeholder' => t('Last Name*'),
      ];
    }

    if (!empty($element['#display_telephone'])) {
      $element['telephone'] = [
        '#type' => 'textfield',
        '#title' => t('Telephone'),
        '#default_value' => $address['telephone'],
        '#required' => TRUE,
        '#placeholder' => t('Telephone*'),
      ];
    }

    $element['street'] = [
      '#type' => 'textfield',
      '#title' => t('Address Line 1'),
      '#default_value' => $address['street'],
      '#required' => TRUE,
      '#placeholder' => t('Address Line 1*'),
    ];

    $element['street2'] = [
      '#type' => 'textfield',
      '#title' => t('Address Line 2'),
      '#default_value' => $address['street2'],
      '#required' => FALSE,
      '#placeholder' => t('Address Line 2'),
    ];

    $element['dynamic_parts'] = [
      '#type' => 'container',
      '#attributes' => [
        'id' => ['dynamic_parts'],
      ],
      '#parents' => $element['#parents'],
    ];

    $element['dynamic_parts']['city'] = [
      '#type' => 'textfield',
      '#title' => $labels['locality'],
      '#default_value' => $address['city'],
      '#required' => TRUE,
      '#placeholder' => $labels['locality'],
    ];

    $dynamic_parts = self::calculateDynamicParts($country);
    $regions = $subdivisionRepository->getList([$country]);

    $possiblyMatchingRegion = "";
    // If address has region and this country has regions to choose from
    // then try to fix the region mess, otherwise set region to "".
    if ($address['region'] && $dynamic_parts['region']['#access']) {
      // Some e-commerce back-ends send back region as an abbreviation and some
      // don't. If we don't have an abbreviation we'll need to flip the regions
      // array in order to find the default value to use.
      $possiblyMatchingRegion = AcmAddress::fixRegionMess($address['region'], $regions);
    }
    $default_region = $possiblyMatchingRegion;

    $element['dynamic_parts']['region'] = [
      '#type' => 'select',
      '#title' => $labels['administrativeArea'],
      '#options' => $regions,
      '#default_value' => $default_region,
      '#empty_option' => '- ' . $labels['administrativeArea'] . ' -',
      '#required' => TRUE,
      '#validated' => TRUE,
    ];

    $element['dynamic_parts']['postcode'] = [
      '#type' => 'textfield',
      '#title' => $labels['postalCode'],
      '#default_value' => $address['postcode'],
      '#required' => TRUE,
      '#placeholder' => $labels['postalCode'],
    ];

    $element['dynamic_parts']['country_id'] = [
      '#type' => 'select',
      '#title' => t('Country'),
      '#options' => $countryRepository->getList(),
      '#default_value' => $country,
      '#required' => TRUE,
      '#ajax' => [
        'callback' => [get_called_class(), 'addressAjaxCallback'],
        'wrapper' => 'dynamic_parts',
        'options' => [
          'query' => [
            'element_parents' => implode('/', $element['#array_parents']),
          ],
        ],
      ],
    ];

    // We created the dynamic parts as we may want them,
    // But now we calculate what is hidden or showing or required
    // and what labels are translated based on the country (not the locale...)
    $element['dynamic_parts'] = array_replace_recursive($element['dynamic_parts'], $dynamic_parts);

    if (!empty($element['#display_billing'])) {
      $element['default_billing'] = [
        '#type' => 'checkbox',
        '#title' => t('Default billing address'),
        '#default_value' => $address['default_billing'],
      ];
    }

    if (!empty($element['#display_shipping'])) {
      $element['default_shipping'] = [
        '#type' => 'checkbox',
        '#title' => t('Default shipping address'),
        '#default_value' => $address['default_shipping'],
      ];
    }

    // TODO (Malachy): Consider adding 'save address to address book' here.
    if (!empty($element['#validate_address'])) {
      $element['#element_validate'] = [[get_called_class(), 'validateAddress']];
      if (!isset($element['#address_review_text'])) {
        $element['#address_review_text'] = t('Address validation suggested a different address.');
      }
      if (!isset($element['#address_failed_text'])) {
        $element['#address_failed_text'] = t('Address validation failed.');
      }
    }

    $element['#tree'] = TRUE;

    return $element;
  }

  /**
   * Validates an acm_address element.
   */
  public static function validateAddress(&$element, FormStateInterface $form_state, &$complete_form) {
    $address = $element['#value'];
    $address_review_text = $element['#address_review_text'];
    $address_failed_text = $element['#address_failed_text'];

    // Make sure these fields have values before trying to validate the address.
    $required_fields = [
      'street',
      'city',
      'region',
      'postcode',
    ];

    $skip = FALSE;

    foreach ($required_fields as $required_field) {
      if (empty($address[$required_field])) {
        $skip = TRUE;
        break;
      }
    }

    // Skip validation if not all fields are filled out yet.
    if ($skip) {
      return $element;
    }

    try {
      $response = \Drupal::service('acm.api')
        ->validateCustomerAddress($address);

      // Address is valid and no suggestion came back.
      if (isset($response['result']['valid']) && empty($response['result']['suggested'])) {
        drupal_set_message($address_review_text, 'status');
      }
      // Address is in review and there's a suggestion that we use to pre-fill
      // the address fields.
      elseif (isset($response['result']['suggested']) && !empty($response['result']['suggested'])) {
        $suggested_address = reset($response['result']['suggested']);
        foreach ($suggested_address as $field => $value) {
          if (!empty($value)) {
            if (isset($element[$field])) {
              $form_state->setValueForElement($element[$field], $value);
            }
            elseif (isset($element['dynamic_parts'][$field])) {
              $form_state->setValueForElement($element['dynamic_parts'][$field], $value);
            }
          }
        }

        drupal_set_message($address_review_text, 'status');
      }
      // Address failed validation.
      else {
        $form_state->setError($element, $address_failed_text);
      }
    }
    catch (RouteException $e) {
      $form_state->setError($element, $address_failed_text);
    }

    return $element;
  }

  /**
   * Ajax handler for country selector.
   *
   * @param array $form
   *   The build form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   The ajax response of the ajax upload.
   */
  public static function addressAjaxCallback(array $form, FormStateInterface $form_state, Request $request) {
    $form_parents = explode('/', $request->query->get('element_parents'));

    // Retrieve the element to be rendered.
    $form = NestedArray::getValue($form, $form_parents);

    $values = $form_state->getValue($form['#parents']);
    $country = $values['country_id'];

    $dynamic_parts = self::calculateDynamicParts($country);
    return array_replace_recursive($form['dynamic_parts'], $dynamic_parts);
  }

  /**
   * Function fixRegionMess().
   *
   * Some ecommerce back-ends send back region as an abbreviation and some
   * don't. If we don't have an abbreviation we'll need to flip the regions
   * array in order to find the default value to use.
   *
   * @param string $region
   *   The region to fix.
   * @param array $regions
   *   The regions to fix against (array of strings expected).
   *
   * @return null|string
   *   The fixed region string.
   */
  public static function fixRegionMess(string $region, array $regions) {

    if ($region) {
      if (!preg_match('/\b([A-Z]{2})\b/', $region)) {
        $flipped_regions = array_flip($regions);
        $region = isset($flipped_regions[$region]) ? $flipped_regions[$region] : "";
      }
    }
    return $region;
  }

}

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

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