You are here

PhoneNumberVerification.php in SMS Framework 2.1.x

File

src/Provider/PhoneNumberVerification.php
View source
<?php

declare (strict_types=1);
namespace Drupal\sms\Provider;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Random;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Utility\Token;
use Drupal\sms\Direction;
use Drupal\sms\Exception\PhoneNumberSettingsException;
use Drupal\sms\Message\SmsMessage;

/**
 * Phone number verification provider.
 */
class PhoneNumberVerification implements PhoneNumberVerificationInterface {

  /**
   * The SMS provider.
   *
   * @var \Drupal\sms\Provider\SmsProviderInterface
   */
  protected $smsProvider;

  /**
   * Storage for phone number settings.
   *
   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
   */
  protected $phoneNumberSettings;

  /**
   * Storage for Phone Number Verification entities.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $phoneNumberVerificationStorage;

  /**
   * The token service.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected $token;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * Time.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $time;

  /**
   * Constructs a new PhoneNumberProvider object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Utility\Token $token
   *   The token replacement system.
   * @param \Drupal\sms\Provider\SmsProviderInterface $sms_provider
   *   The SMS provider.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   Time.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, Token $token, SmsProviderInterface $sms_provider, TimeInterface $time) {
    $this->smsProvider = $sms_provider;
    $this->phoneNumberSettings = $entity_type_manager
      ->getStorage('phone_number_settings');
    $this->phoneNumberVerificationStorage = $entity_type_manager
      ->getStorage('sms_phone_number_verification');
    $this->token = $token;
    $this->configFactory = $config_factory;
    $this->time = $time;
  }

  /**
   * {@inheritdoc}
   */
  public function getPhoneNumberSettings($entity_type_id, $bundle) {
    return $this->phoneNumberSettings
      ->load($entity_type_id . '.' . $bundle);
  }

  /**
   * {@inheritdoc}
   */
  public function getPhoneNumberSettingsForEntity(EntityInterface $entity) {
    if (!($phone_number_settings = $this
      ->getPhoneNumberSettings($entity
      ->getEntityTypeId(), $entity
      ->bundle()))) {
      throw new PhoneNumberSettingsException(sprintf('Entity phone number config does not exist for bundle %s:%s', $entity
        ->getEntityTypeId(), $entity
        ->bundle()));
    }
    return $phone_number_settings;
  }

  /**
   * {@inheritdoc}
   */
  public function getPhoneVerificationByCode($code) {
    $entities = $this->phoneNumberVerificationStorage
      ->loadByProperties([
      'code' => $code,
    ]);
    return reset($entities);
  }

  /**
   * {@inheritdoc}
   */
  public function getPhoneVerificationByPhoneNumber($phone_number, $verified = TRUE, $entity_type = NULL) {
    $properties['phone'] = $phone_number;
    if (isset($entity_type)) {
      $properties['entity__target_type'] = $entity_type;
    }
    if (isset($verified)) {
      $properties['status'] = (int) $verified;
    }
    return $this->phoneNumberVerificationStorage
      ->loadByProperties($properties);
  }

  /**
   * {@inheritdoc}
   */
  public function getPhoneVerificationByEntity(EntityInterface $entity, $phone_number) {
    $entities = $this->phoneNumberVerificationStorage
      ->loadByProperties([
      'entity__target_id' => $entity
        ->id(),
      'entity__target_type' => $entity
        ->getEntityTypeId(),
      'phone' => $phone_number,
    ]);
    return reset($entities);
  }

  /**
   * {@inheritdoc}
   */
  public function newPhoneVerification(EntityInterface $entity, $phone_number) {
    $config = $this
      ->getPhoneNumberSettingsForEntity($entity);
    $message = $config
      ->getVerificationMessage() ?: '';

    // @todo Replace with code generator.
    $random = new Random();
    $code = strtoupper($random
      ->name(6));

    /** @var \Drupal\sms\Entity\PhoneNumberVerificationInterface $phone_verification */
    $phone_verification = $this->phoneNumberVerificationStorage
      ->create();
    $phone_verification
      ->setCode($code)
      ->setStatus(FALSE)
      ->setPhoneNumber($phone_number)
      ->setEntity($entity)
      ->save();
    if ($phone_verification) {
      $sms_message = new SmsMessage();
      $sms_message
        ->addRecipient($phone_number)
        ->setOption('_is_verification_message', TRUE)
        ->setMessage($message)
        ->setDirection(Direction::OUTGOING);
      $data['sms-message'] = $sms_message;
      $data['sms_verification_code'] = $phone_verification
        ->getCode();
      $sms_message
        ->setMessage($this->token
        ->replace($message, $data))
        ->setAutomated(FALSE);
      $this->smsProvider
        ->queue($sms_message);
    }
    return $phone_verification;
  }

  /**
   * {@inheritdoc}
   */
  public function updatePhoneVerificationByEntity(EntityInterface $entity) {
    try {
      $phone_number_settings = $this
        ->getPhoneNumberSettingsForEntity($entity);
      $field_name = $phone_number_settings
        ->getFieldName('phone_number');
      if (!empty($field_name)) {
        $items_original = isset($entity->original) ? $entity->original->{$field_name} : NULL;
        $items = $entity->{$field_name};
      }
    } catch (PhoneNumberSettingsException $e) {

      // Missing phone number configuration for this entity.
    }

    // $items can be unassigned because field_name is not configured, or is NULL
    // because there is no items.
    if (!isset($items)) {
      return;
    }
    $numbers = [];
    assert($items instanceof FieldItemListInterface);
    foreach ($items as $item) {
      $phone_number = $item->value;
      $numbers[] = $phone_number;
      if (!$this
        ->getPhoneVerificationByEntity($entity, $phone_number)) {
        $this
          ->newPhoneVerification($entity, $phone_number);
      }
    }
    if (isset($items_original) && !$items
      ->equals($items_original)) {
      foreach ($items_original as $item) {
        $phone_number = $item->value;

        // A phone number was deleted.
        if (!in_array($phone_number, $numbers)) {
          if ($phone_verification = $this
            ->getPhoneVerificationByEntity($entity, $phone_number)) {
            $phone_verification
              ->delete();
          }
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function deletePhoneVerificationByEntity(EntityInterface $entity) {

    // Check the entity uses phone numbers. To save on a SQL call, and to
    // prevent having to install phone number verification for SMS Framework
    // tests which delete entities. Which would otherwise error on non-existent
    // tables.
    try {
      $this
        ->getPhoneNumberSettingsForEntity($entity);
      $verification_entities = $this->phoneNumberVerificationStorage
        ->loadByProperties([
        'entity__target_id' => $entity
          ->id(),
        'entity__target_type' => $entity
          ->getEntityTypeId(),
      ]);
      $this->phoneNumberVerificationStorage
        ->delete($verification_entities);
    } catch (PhoneNumberSettingsException $e) {
    }
  }

  /**
   * {@inheritdoc}
   */
  public function purgeExpiredVerifications() {
    $current_time = $this->time
      ->getRequestTime();
    $verification_ids = [];
    foreach ($this->configFactory
      ->listAll('sms.phone.') as $config_id) {
      $config = $this->configFactory
        ->get($config_id);
      $lifetime = $config
        ->get('verification_code_lifetime');
      if (!empty($lifetime)) {
        $verification_ids += $this->phoneNumberVerificationStorage
          ->getQuery()
          ->condition('entity__target_type', $config
          ->get('entity_type'))
          ->condition('bundle', $config
          ->get('bundle'))
          ->condition('status', 0)
          ->condition('created', $current_time - $lifetime, '<')
          ->execute();
      }
    }

    /** @var \Drupal\sms\Entity\PhoneNumberVerificationInterface $phone_number_verification */
    foreach ($this->phoneNumberVerificationStorage
      ->loadMultiple($verification_ids) as $phone_number_verification) {
      if ($entity = $phone_number_verification
        ->getEntity()) {
        try {
          $config = $this
            ->getPhoneNumberSettingsForEntity($entity);
          $purge = $config
            ->getPurgeVerificationPhoneNumber();
          $field_name = $config
            ->getFieldName('phone_number');
          if (!empty($purge) && isset($entity->{$field_name})) {
            $entity->{$field_name}
              ->filter(function ($item) use ($phone_number_verification) {
              return $item->value != $phone_number_verification
                ->getPhoneNumber();
            });
            $entity
              ->save();
          }
        } catch (EntityStorageException $e) {

          // Failed to save entity.
        }
      }
      $this->phoneNumberVerificationStorage
        ->delete([
        $phone_number_verification,
      ]);
    }
  }

}

Classes

Namesort descending Description
PhoneNumberVerification Phone number verification provider.