You are here

class MobileNumber in Mobile Number 7

Class MobileNumber handles mobile number validation and verification.

@package Drupal\mobile_number

Hierarchy

Expanded class hierarchy of MobileNumber

File

src/MobileNumber.php, line 20

View source
class MobileNumber implements MobileNumberInterface {
  const ERROR_INVALID_NUMBER = 1;
  const ERROR_WRONG_TYPE = 2;
  const ERROR_WRONG_COUNTRY = 3;
  const ERROR_NO_NUMBER = 4;
  const VERIFY_WRONG_CODE = 5;
  const VERIFY_TOO_MANY_ATTEMPTS = 6;
  const VERIFY_SMS_FAILED = 7;
  const VERIFY_ATTEMPTS_COUNT = 5;
  const VERIFY_ATTEMPTS_INTERVAL = 3600;
  const SMS_ATTEMPTS_INTERVAL = 60;
  const SMS_ATTEMPTS_COUNT = 1;

  /**
   * The PhoneNumberUtil object.
   *
   * @var \libphonenumber\PhoneNumberUtil
   */
  public $libUtil;

  /**
   * The PhoneNumber object of the number.
   *
   * @var \libphonenumber\PhoneNumber
   */
  public $libPhoneNumber;

  /**
   * Country code.
   *
   * @var string
   */
  public $country;

  /**
   * The version of the number in the format local to the country.
   *
   * @var string
   */
  public $localNumber;

  /**
   * The international callable version of the number.
   *
   * @var string
   */
  public $callableNumber;

  /**
   * MobileNumber constructor.
   *
   * @param string $number
   *   Number.
   * @param null|string $country
   *   Country.
   * @param array $types
   *   Allowed phone number types.
   *
   * @throws \Exception
   */
  public function __construct($number, $country = NULL, $types = array(
    1 => 1,
    2 => 2,
  )) {
    if (!$number) {
      throw new \Exception('Empty number', $this::ERROR_NO_NUMBER);
    }
    $this->libUtil = PhoneNumberUtil::getInstance();
    try {
      $phone_number = $this->libUtil
        ->parse($number, $country);
    } catch (\Exception $e) {
      throw new \Exception('Invalid number', $this::ERROR_INVALID_NUMBER);
    }
    if ($types) {
      if (!in_array($this->libUtil
        ->getNumberType($phone_number), $types)) {
        throw new \Exception('Not a mobile number', $this::ERROR_WRONG_TYPE);
      }
    }
    $this->country = $this->libUtil
      ->getRegionCodeForNumber($phone_number);
    $national_number = $phone_number
      ->getNationalNumber();
    $prefix = $this->libUtil
      ->getNddPrefixForRegion($this->country, TRUE);
    $this->localNumber = $prefix . $national_number;
    if ($country && $this->country != $country) {
      throw new \Exception('Wrong country', $this::ERROR_WRONG_COUNTRY);
    }
    $this->callableNumber = $this->libUtil
      ->format($phone_number, PhoneNumberFormat::E164);
    $this->libPhoneNumber = $phone_number;
    $this->verificationToken = '';
  }

  /**
   * String typecasting.
   *
   * @return string
   *   Callable number.
   */
  public function __toString() {
    return $this->callableNumber;
  }

  /**
   * {@inheritdoc}
   */
  public function toArray() {
    return array(
      'callable_number' => $this->callableNumber,
      'country' => $this->country,
      'local_number' => $this->localNumber,
      'verified' => $this
        ->isVerified(),
    );
  }

  /**
   * {@inheritdoc}
   */
  public static function getCountryCode($country) {
    $libUtil = PhoneNumberUtil::getInstance();
    return $libUtil
      ->getCountryCodeForRegion($country);
  }

  /**
   * {@inheritdoc}
   */
  public static function getCountryOptions($filter = array(), $show_country_names = FALSE) {
    $libUtil = PhoneNumberUtil::getInstance();
    $regions = $libUtil
      ->getSupportedRegions();
    $countries = array();
    foreach ($regions as $region => $country) {
      $code = $libUtil
        ->getCountryCodeForRegion($country);
      if (!$filter || !empty($filter[$country])) {
        $name = MobileNumber::getCountryName($country);
        $countries[$country] = $show_country_names && $name ? "{$name} (+{$code})" : "{$country} (+{$code})";
      }
    }
    asort($countries);
    return $countries;
  }

  /**
   * {@inheritdoc}
   */
  public static function getCountryName($country) {
    include_once DRUPAL_ROOT . '/includes/locale.inc';
    $drupal_countries = country_get_list();
    return !empty($drupal_countries[$country]) ? $drupal_countries[$country] : '';
  }

  /**
   * {@inheritdoc}
   */
  public static function generateVerificationCode($length = 4) {
    return str_pad((string) rand(0, pow(10, $length)), $length, '0', STR_PAD_LEFT);
  }

  /**
   * {@inheritdoc}
   */
  public function checkFlood($type = 'verification') {
    switch ($type) {
      case 'verification':
        return flood_is_allowed('mobile_number_verification', $this::VERIFY_ATTEMPTS_COUNT, $this::VERIFY_ATTEMPTS_INTERVAL, $this->callableNumber);
      case 'sms':
        return flood_is_allowed('mobile_number_sms', $this::SMS_ATTEMPTS_COUNT, $this::SMS_ATTEMPTS_INTERVAL, $this->callableNumber) && flood_is_allowed('mobile_number_sms_ip', $this::SMS_ATTEMPTS_COUNT * 5, $this::SMS_ATTEMPTS_INTERVAL * 5);
      default:
        return TRUE;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getToken() {
    if (!empty($_SESSION['mobile_number_verification'][$this->callableNumber]['token'])) {
      return $_SESSION['mobile_number_verification'][$this->callableNumber]['token'];
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function sendVerification($message, $code, $token_data = array()) {
    $message = t($message, array(
      '!code' => $code,
      '!site_name' => variable_get('site_name', $_SERVER['SERVER_NAME']),
    ));
    if (module_exists('token')) {
      $message = token_replace($message, $token_data);
    }
    flood_register_event('mobile_number_sms', $this::SMS_ATTEMPTS_INTERVAL, $this->callableNumber);
    flood_register_event('mobile_number_sms_ip', $this::SMS_ATTEMPTS_INTERVAL * 5);
    if (mobile_number_send_sms($this->callableNumber, $message)) {
      $token = $this
        ->registerVerificationCode($code, $this->callableNumber);
      $_SESSION['mobile_number_verification'][$this->callableNumber] = array(
        'token' => $token,
        'verified' => FALSE,
      );
      return $token;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public static function registerVerificationCode($code, $number) {
    $time = time();
    $token = drupal_get_token(rand(0, 999999999) . $time . 'mobile verification token' . $number);
    $hash = static::codeHash($token, $code, $number);
    db_insert('mobile_number_verification')
      ->fields(array(
      'token' => $token,
      'timestamp' => $time,
      'verification_code' => $hash,
    ))
      ->execute();
    return $token;
  }

  /**
   * {@inheritdoc}
   */
  public function verifyCode($code, $token = NULL) {
    if ($code && ($this
      ->getToken() || $token)) {
      $token = $token ? $token : $this
        ->getToken();
      $hash = $this
        ->codeHash($token, $code, $this->callableNumber);
      $query = db_select('mobile_number_verification', 'm');
      $query
        ->fields('m', array(
        'token',
      ))
        ->condition('token', $token)
        ->condition('timestamp', time() - 60 * 60 * 24, '>')
        ->condition('verification_code', $hash);
      $result = $query
        ->execute()
        ->fetchAssoc();
      if ($result) {
        $_SESSION['mobile_number_verification'][$this->callableNumber]['verified'] = TRUE;
        return TRUE;
      }
      flood_register_event('mobile_number_verification', $this::VERIFY_ATTEMPTS_INTERVAL, $this->callableNumber);
      return FALSE;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function isVerified() {
    return !empty($_SESSION['mobile_number_verification'][$this->callableNumber]['verified']);
  }

  /**
   * {@inheritdoc}
   */
  public static function codeHash($token, $code, $number) {
    $secret = variable_get('mobile_number_secret', '');
    return sha1("{$number}{$secret}{$token}{$code}");
  }

}

Members

Namesort descending Modifiers Type Description Overrides
MobileNumber::$callableNumber public property The international callable version of the number.
MobileNumber::$country public property Country code.
MobileNumber::$libPhoneNumber public property The PhoneNumber object of the number.
MobileNumber::$libUtil public property The PhoneNumberUtil object.
MobileNumber::$localNumber public property The version of the number in the format local to the country.
MobileNumber::checkFlood public function Checks whether there were too many verifications attempted with the current number. Overrides MobileNumberInterface::checkFlood
MobileNumber::codeHash public static function Generate hash given token and code. Overrides MobileNumberInterface::codeHash
MobileNumber::ERROR_INVALID_NUMBER constant
MobileNumber::ERROR_NO_NUMBER constant
MobileNumber::ERROR_WRONG_COUNTRY constant
MobileNumber::ERROR_WRONG_TYPE constant
MobileNumber::generateVerificationCode public static function Generates a random numeric string. Overrides MobileNumberInterface::generateVerificationCode
MobileNumber::getCountryCode public static function Gets the country phone number prefix given a country code. Overrides MobileNumberInterface::getCountryCode
MobileNumber::getCountryName public static function Gets the country name a country code. Overrides MobileNumberInterface::getCountryName
MobileNumber::getCountryOptions public static function Get all supported countries. Overrides MobileNumberInterface::getCountryOptions
MobileNumber::getToken public function Gets token generated if verification code was sent. Overrides MobileNumberInterface::getToken
MobileNumber::isVerified public function Is the number already verified. Overrides MobileNumberInterface::isVerified
MobileNumber::registerVerificationCode public static function Registers code for mobile number and returns it's token. Overrides MobileNumberInterface::registerVerificationCode
MobileNumber::sendVerification public function Send verification code to mobile number. Overrides MobileNumberInterface::sendVerification
MobileNumber::SMS_ATTEMPTS_COUNT constant
MobileNumber::SMS_ATTEMPTS_INTERVAL constant
MobileNumber::toArray public function Converts object to an array. Overrides MobileNumberInterface::toArray
MobileNumber::verifyCode public function Verifies input code matches code sent to user. Overrides MobileNumberInterface::verifyCode
MobileNumber::VERIFY_ATTEMPTS_COUNT constant
MobileNumber::VERIFY_ATTEMPTS_INTERVAL constant
MobileNumber::VERIFY_SMS_FAILED constant
MobileNumber::VERIFY_TOO_MANY_ATTEMPTS constant
MobileNumber::VERIFY_WRONG_CODE constant
MobileNumber::__construct public function MobileNumber constructor.
MobileNumber::__toString public function String typecasting.