You are here

UserManager.php in Social Auth 3.x

Same filename and directory in other branches
  1. 8.2 src/User/UserManager.php

File

src/User/UserManager.php
View source
<?php

namespace Drupal\social_auth\User;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Transliteration\PhpTransliteration;
use Drupal\Core\Utility\Token;
use Drupal\social_api\User\UserManager as SocialApiUserManager;
use Drupal\social_auth\Event\UserFieldsEvent;
use Drupal\social_auth\Event\SocialAuthEvents;
use Drupal\social_auth\Event\UserEvent;
use Drupal\social_auth\SettingsTrait;
use Drupal\user\UserInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Drupal\social_auth\Entity\SocialAuth;

/**
 * Manages database related tasks.
 */
class UserManager extends SocialApiUserManager {
  use StringTranslationTrait;
  use SettingsTrait;

  /**
   * Used for access Drupal user field definitions.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityFieldManager;

  /**
   * Used for user picture directory and file transliteration.
   *
   * @var \Drupal\Core\Transliteration\PhpTransliteration
   */
  protected $transliteration;

  /**
   * Used to get the current UI language.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * Event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * Used for token support in Drupal user picture directory.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected $token;

  /**
   * Used for saving the profile picture of the users.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * Constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   Used for loading and creating Drupal user.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   Used to display messages to user.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   Used for logging errors.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   Used for accessing Drupal configuration.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   Used for access Drupal user field definitions.
   * @param \Drupal\Core\Transliteration\PhpTransliteration $transliteration
   *   Used for user picture directory and file transliteration.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   Used to get current UI language.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   Used for dispatching social auth events.
   * @param \Drupal\Core\Utility\Token $token
   *   Used for token support in Drupal user picture directory.
   * @param \Drupal\Core\File\FileSystemInterface $file_system
   *   Used for saving the profile picture of the users.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, MessengerInterface $messenger, LoggerChannelFactoryInterface $logger_factory, ConfigFactoryInterface $config_factory, EntityFieldManagerInterface $entity_field_manager, PhpTransliteration $transliteration, LanguageManagerInterface $language_manager, EventDispatcherInterface $event_dispatcher, Token $token, FileSystemInterface $file_system) {
    parent::__construct('social_auth', $entity_type_manager, $messenger, $logger_factory);
    $this->configFactory = $config_factory;
    $this->entityFieldManager = $entity_field_manager;
    $this->transliteration = $transliteration;
    $this->languageManager = $language_manager;
    $this->eventDispatcher = $event_dispatcher;
    $this->token = $token;
    $this->fileSystem = $file_system;
  }

  /**
   * Creates a new user.
   *
   * @param \Drupal\social_auth\User\SocialAuthUserInterface $user
   *   The data of the user to be created.
   *
   * @return \Drupal\user\UserInterface|null
   *   The Drupal user if successful
   *   Null otherwise.
   */
  public function createNewUser(SocialAuthUserInterface $user) {

    // Download profile picture for the newly created user.
    if ($user
      ->getPictureUrl()) {
      $this
        ->setProfilePic($user);
    }
    $drupal_user = $this
      ->createUser($user);
    if ($drupal_user) {

      // If the new user could be registered.
      $this
        ->addUserRecord($drupal_user
        ->id(), $user
        ->getProviderId(), $user
        ->getToken(), $user
        ->getAdditionalData());
      if ($this
        ->saveUser($drupal_user)) {
        return $drupal_user;
      }
    }
    return NULL;
  }

  /**
   * Create a new user account.
   *
   * @param \Drupal\social_auth\User\SocialAuthUserInterface $user
   *   The data of the user to be created.
   *
   * @return \Drupal\user\Entity\User|false
   *   Drupal user account if user was created
   *   False otherwise
   */
  public function createUser(SocialAuthUserInterface $user) {
    $name = $user
      ->getName();
    $email = $user
      ->getEmail();

    // Check if site configuration allows new users to register.
    if ($this
      ->isRegistrationDisabled()) {
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->warning('Failed to create user. User registration is disabled. Name: @name, email: @email.', [
        '@name' => $name,
        '@email' => $email,
      ]);
      $this->messenger
        ->addError($this
        ->t('User registration is disabled, please contact the administrator.'));
      return FALSE;
    }

    // Make sure we have everything we need.
    if (!$name) {
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->error('Failed to create user. Name: @name', [
        '@name' => $name,
      ]);
      $this->messenger
        ->addError($this
        ->t('You could not be authenticated, please contact the administrator.'));
      return FALSE;
    }

    // Get the current UI language.
    $langcode = $this->languageManager
      ->getCurrentLanguage()
      ->getId();

    // Try to save the new user account.
    try {

      // Initializes the user fields.
      $fields = $this
        ->getUserFields($user, $langcode);

      /** @var \Drupal\user\Entity\User $new_user */
      $new_user = $this->entityTypeManager
        ->getStorage('user')
        ->create($fields);
      $new_user
        ->save();
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->notice('New user created. Username @username, UID: @uid', [
        '@username' => $new_user
          ->getAccountName(),
        '@uid' => $new_user
          ->id(),
      ]);

      // Dispatches SocialAuthEvents::USER_CREATED event.
      $event = new UserEvent($new_user, $this
        ->getPluginId(), $user);
      $this->eventDispatcher
        ->dispatch(SocialAuthEvents::USER_CREATED, $event);
      return $new_user;
    } catch (\Exception $ex) {
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->error('Could not create new user. Exception: @message', [
        '@message' => $ex
          ->getMessage(),
      ]);
    }
    $this->messenger
      ->addError($this
      ->t('You could not be authenticated, please contact the administrator.'));
    return FALSE;
  }

  /**
   * Add user record in Social Auth Entity.
   *
   * @param int $user_id
   *   Drupal User ID.
   * @param string $provider_user_id
   *   Unique Social ID returned by social network.
   * @param string $token
   *   For making API calls.
   * @param array|null $user_data
   *   Additional user data collected.
   *
   * @return true
   *   if user record is added in social_auth entity table
   *   Else false.
   */
  public function addUserRecord($user_id, $provider_user_id, $token, $user_data) {

    // Make sure we have everything we need.
    if (!$user_id || !$this->pluginId || !$provider_user_id) {
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->error('Failed to add user record in Social Auth entity.
          User_id: @user_id, social_network_identifier: @social_network_identifier, provider_user_id : @provider_user_id ', [
        '@user_id' => $user_id,
        '@social_network_identifier' => $this->pluginId,
        '@provider_user_id ' => $provider_user_id,
      ]);
      $this->messenger
        ->addError($this
        ->t('You could not be authenticated, please contact the administrator.'));
      return FALSE;
    }
    else {

      // Add user record.
      $values = [
        'user_id' => $user_id,
        'plugin_id' => $this->pluginId,
        'provider_user_id' => $provider_user_id,
        'additional_data' => $user_data,
        'token' => $token,
      ];
      try {
        $user_info = SocialAuth::create($values);

        // Save the entity.
        $user_info
          ->save();
      } catch (\Exception $ex) {
        $this->loggerFactory
          ->get($this
          ->getPluginId())
          ->error('Failed to add user record in Social Auth entity.
            Exception: @message', [
          '@message' => $ex
            ->getMessage(),
        ]);
        $this->messenger
          ->addError($this
          ->t('You could not be authenticated, please contact the administrator.'));
        return FALSE;
      }
      return TRUE;
    }
  }

  /**
   * Loads existing Drupal user object by given property and value.
   *
   * Note that first matching user is returned. Email address and account name
   * are unique so there can be only zero or one matching user when
   * loading users by these properties.
   *
   * @param string $field
   *   User entity field to search from.
   * @param string $value
   *   Value to search for.
   *
   * @return \Drupal\user\Entity\User|false
   *   Drupal user account if found
   *   False otherwise
   */
  public function loadUserByProperty($field, $value) {
    try {
      $users = $this->entityTypeManager
        ->getStorage('user')
        ->loadByProperties([
        $field => $value,
      ]);
      if (!empty($users)) {
        return current($users);
      }
    } catch (\Exception $ex) {
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->error('Failed to load user. Exception: @message', [
        '@message' => $ex
          ->getMessage(),
      ]);
    }

    // If user was not found, return FALSE.
    return FALSE;
  }

  /**
   * Saves the Drupal user entity.
   *
   * @return bool
   *   True if picture was successfully set.
   *   False otherwise.
   */
  protected function saveUser(UserInterface $drupal_user) {
    try {
      $drupal_user
        ->save();
      return TRUE;
    } catch (EntityStorageException $ex) {
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->error('Failed to save user. Exception: @message', [
        '@message' => $ex
          ->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Downloads and sets user profile picture.
   *
   * @param \Drupal\social_auth\User\SocialAuthUserInterface $user
   *   The Social Auth User object.
   *
   * @return bool
   *   True if picture was successfully set.
   *   False otherwise.
   */
  protected function setProfilePic(SocialAuthUserInterface $user) {
    $picture_url = $user
      ->getPictureUrl();
    $id = $user
      ->getProviderId();

    // Tries to download the profile picture and add it to Social Auth User.
    if ($this
      ->userPictureEnabled()) {
      $file = $this
        ->downloadProfilePic($picture_url, $id);
      if ($file) {
        $user
          ->setPicture($file
          ->id());
      }
    }
    return FALSE;
  }

  /**
   * Downloads the profile picture to Drupal filesystem.
   *
   * @param string $picture_url
   *   Absolute URL where to download the profile picture.
   * @param string $id
   *   Social network ID of the user.
   *
   * @return \Drupal\file\FileInterface|false
   *   FileInterface object if file was successfully downloaded
   *   False otherwise
   */
  protected function downloadProfilePic($picture_url, $id) {

    // Make sure that we have everything we need.
    if (!$picture_url || !$id) {
      return FALSE;
    }

    // Determine target directory.
    $scheme = $this->configFactory
      ->get('system.file')
      ->get('default_scheme');
    $file_directory = $this
      ->getPictureDirectory();
    if (!$file_directory) {
      return FALSE;
    }
    $directory = $scheme . '://' . $file_directory;

    // Replace tokens.
    $directory = $this->token
      ->replace($directory);

    // Transliterate directory name.
    $directory = $this->transliteration
      ->transliterate($directory, 'en', '_', 50);
    if (!$this->fileSystem
      ->prepareDirectory($directory, $this->fileSystem::CREATE_DIRECTORY)) {
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->error('Could not save @plugin_id\'s provider profile picture. Directory is not writable: @directory', [
        '@directory' => $directory,
        '@provider' => $this
          ->getPluginId(),
      ]);
      return FALSE;
    }

    // Generate filename and transliterate.
    $filename = $this->transliteration
      ->transliterate($this
      ->getPluginId() . '_' . $id, 'en', '_', 50) . '.jpg';
    $destination = $directory . DIRECTORY_SEPARATOR . $filename;

    // Download the picture to local filesystem.
    if (!($file = $this
      ->systemRetrieveFile($picture_url, $destination, TRUE, 1))) {
      $this->loggerFactory
        ->get($this
        ->getPluginId())
        ->error('Could not download @plugin_id\'s provider profile picture from url: @url', [
        '@url' => $picture_url,
        '@plugin_id' => $this
          ->getPluginId(),
      ]);
      return FALSE;
    }
    return $file;
  }

  /**
   * Ensures that Drupal usernames will be unique.
   *
   * Drupal usernames will be generated so that the user's full name on provider
   * will become user's Drupal username. This method will check if the username
   * is already used and appends a number until it finds the first available
   * username.
   *
   * @param string $name
   *   User's full name on provider.
   *
   * @return string
   *   Unique drupal username.
   */
  protected function generateUniqueUsername($name) {
    $max_length = 60;
    $name = mb_substr($name, 0, $max_length);
    $name = str_replace(' ', '', $name);
    $name = strtolower($name);

    // Add a trailing number if needed to make username unique.
    $base = $name;
    $i = 1;
    $candidate = $base;
    while ($this
      ->loadUserByProperty('name', $candidate)) {

      // Calculate max length for $base and truncate if needed.
      $max_length_base = $max_length - strlen((string) $i) - 1;
      $base = mb_substr($base, 0, $max_length_base);
      $candidate = $base . $i;
      $i++;
    }

    // Trim leading and trailing whitespace.
    $candidate = trim($candidate);
    return $candidate;
  }

  /**
   * Returns an array of fields to initialize the creation of the user.
   *
   * @param \Drupal\social_auth\User\SocialAuthUserInterface $user
   *   The data of the user to be created.
   * @param string $langcode
   *   The current UI language.
   *
   * @return array
   *   Fields to initialize for the user creation.
   */
  protected function getUserFields(SocialAuthUserInterface $user, $langcode) {
    $fields = [
      'name' => $this
        ->generateUniqueUsername($user
        ->getName()),
      'mail' => $user
        ->getEmail(),
      'init' => $user
        ->getEmail(),
      'pass' => $this
        ->userPassword(32),
      'status' => $this
        ->getNewUserStatus(),
      'langcode' => $langcode,
      'preferred_langcode' => $langcode,
      'preferred_admin_langcode' => $langcode,
      'user_picture' => $user
        ->getPicture(),
    ];

    // Dispatches SocialAuthEvents::USER_FIELDS, so that other modules can
    // update this array before an user is saved.
    $event = new UserFieldsEvent($fields, $this
      ->getPluginId(), $user);
    $this->eventDispatcher
      ->dispatch(SocialAuthEvents::USER_FIELDS, $event);
    $fields = $event
      ->getUserFields();
    return $fields;
  }

  /**
   * Returns whether this site supports the default user picture feature.
   *
   * @return bool
   *   True if user pictures are enabled
   *   False otherwise
   */
  protected function userPictureEnabled() {
    $field_definitions = $this->entityFieldManager
      ->getFieldDefinitions('user', 'user');
    return isset($field_definitions['user_picture']);
  }

  /**
   * Returns picture directory if site supports the user picture feature.
   *
   * @return string|bool
   *   Directory for user pictures if site supports user picture feature.
   *   False otherwise.
   */
  protected function getPictureDirectory() {
    $field_definitions = $this->entityFieldManager
      ->getFieldDefinitions('user', 'user');
    if (isset($field_definitions['user_picture'])) {
      return $field_definitions['user_picture']
        ->getSetting('file_directory');
    }
    return FALSE;
  }

  /**
   * Wrapper for user_password.
   *
   * We need to wrap the legacy procedural Drupal API functions so that we are
   * not using them directly in our own methods. This way we can unit test our
   * own methods.
   *
   * @param int $length
   *   Length of the password.
   *
   * @return string
   *   The password.
   *
   * @see user_password
   */
  protected function userPassword($length) {
    return user_password($length);
  }

  /**
   * Wrapper for system_retrieve_file.
   *
   * We need to wrap the legacy procedural Drupal API functions so that we are
   * not using them directly in our own methods. This way we can unit test our
   * own methods.
   *
   * @see system_retrieve_file
   */
  protected function systemRetrieveFile($url, $destination, $managed, $replace) {
    return system_retrieve_file($url, $destination, $managed, $replace);
  }

}

Classes

Namesort descending Description
UserManager Manages database related tasks.