You are here

class SimpleFbConnectUserManager in Simple FB Connect 8.2

Same name and namespace in other branches
  1. 8.3 src/SimpleFbConnectUserManager.php \Drupal\simple_fb_connect\SimpleFbConnectUserManager

Contains all logic that is related to Drupal user management.

Hierarchy

Expanded class hierarchy of SimpleFbConnectUserManager

2 files declare their use of SimpleFbConnectUserManager
SimpleFbConnectController.php in src/Controller/SimpleFbConnectController.php
Contains \Drupal\simple_fb_connect\Controller\SimpleFbConnectController.
TestSimpleFbConnectUserManager.php in tests/src/Unit/TestSimpleFbConnectUserManager.php
Contains Drupal\Tests\simple_fb_connect\Unit\SimpleFbConnectTestUserManager.
1 string reference to 'SimpleFbConnectUserManager'
simple_fb_connect.services.yml in ./simple_fb_connect.services.yml
simple_fb_connect.services.yml
1 service uses SimpleFbConnectUserManager
simple_fb_connect.user_manager in ./simple_fb_connect.services.yml
Drupal\simple_fb_connect\SimpleFbConnectUserManager

File

src/SimpleFbConnectUserManager.php, line 27
Contains \Drupal\simple_fb_connect\SimpleFbConnectUserManager.

Namespace

Drupal\simple_fb_connect
View source
class SimpleFbConnectUserManager {
  use StringTranslationTrait;
  protected $configFactory;
  protected $loggerFactory;
  protected $eventDispatcher;
  protected $entityTypeManager;
  protected $entityFieldManager;
  protected $token;
  protected $transliteration;

  /**
   * Constructor.
   */
  public function __construct(ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, TranslationInterface $string_translation, EventDispatcherInterface $event_dispatcher, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, Token $token, PhpTransliteration $transliteration) {
    $this->configFactory = $config_factory;
    $this->loggerFactory = $logger_factory;
    $this->stringTranslation = $string_translation;
    $this->eventDispatcher = $event_dispatcher;
    $this->entityTypeManager = $entity_type_manager;
    $this->entityFieldManager = $entity_field_manager;
    $this->token = $token;
    $this->transliteration = $transliteration;
  }

  /**
   * 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 ore 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) {
    $users = $this->entityTypeManager
      ->getStorage('user')
      ->loadByProperties(array(
      $field => $value,
    ));
    if (!empty($users)) {
      return current($users);
    }

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

  /**
   * Create a new user account.
   *
   * @param string $name
   *   User's name on Facebook.
   * @param string $email
   *   User's email address.
   *
   * @return \Drupal\user\Entity\User|false
   *   Drupal user account if user was created
   *   False otherwise
   */
  public function createUser($name, $email) {

    // Make sure we have everyting we need.
    if (!$name || !$email) {
      $this->loggerFactory
        ->get('simple_fb_connect')
        ->error('Failed to create user. Name: @name, email: @email', array(
        '@name' => $name,
        '@email' => $email,
      ));
      $this
        ->drupalSetMessage($this
        ->t('Error while creating user account. Please contact site administrator.'), 'error');
      return FALSE;
    }

    // Check if site configuration allows new users to register.
    if ($this
      ->registrationBlocked()) {
      $this->loggerFactory
        ->get('simple_fb_connect')
        ->warning('Failed to create user. User registration is disabled in Drupal account settings. Name: @name, email: @email.', array(
        '@name' => $name,
        '@email' => $email,
      ));
      $this
        ->drupalSetMessage($this
        ->t('Only existing users can log in with Facebook. Contact system administrator.'), 'error');
      return FALSE;
    }

    // Set up the user fields.
    // - Username will be user's name on Facebook.
    // - Password can be very long since the user doesn't see this.
    $fields = array(
      'name' => $this
        ->generateUniqueUsername($name),
      'mail' => $email,
      'init' => $email,
      'pass' => $this
        ->userPassword(32),
      'status' => $this
        ->getNewUserStatus(),
    );

    // Create new user account.
    $new_user = $this->entityTypeManager
      ->getStorage('user')
      ->create($fields);

    // Try to save the new user account.
    try {
      $new_user
        ->save();
      $this->loggerFactory
        ->get('simple_fb_connect')
        ->notice('New user created. Username @username, UID: @uid', array(
        '@username' => $new_user
          ->getAccountName(),
        '@uid' => $new_user
          ->id(),
      ));

      // Dispatch an event so that other modules can react to the user creation.
      // Set the account twice on the event: as the main subject but also in the
      // list of arguments.
      $event = new GenericEvent($new_user, [
        'account' => $new_user,
      ]);
      $this->eventDispatcher
        ->dispatch('simple_fb_connect.user_created', $event);
      return $new_user;
    } catch (EntityStorageException $ex) {
      $this
        ->drupalSetMessage($this
        ->t('Creation of user account failed. Please contact site administrator.'), 'error');
      $this->loggerFactory
        ->get('simple_fb_connect')
        ->error('Could not create new user. Exception: @message', array(
        '@message' => $ex
          ->getMessage(),
      ));
    }
    return FALSE;
  }

  /**
   * Logs the user in.
   *
   * @todo Add Boost integtraion when Boost is available for D8
   *   https://www.drupal.org/node/2524372
   *
   * @param \Drupal\user\Entity\User $drupal_user
   *   User object.
   *
   * @return bool
   *   True if login was successful
   *   False if the login was blocked
   */
  public function loginUser(User $drupal_user) {

    // Prevent admin login if defined in module settings.
    if ($this
      ->loginDisabledForAdmin($drupal_user)) {
      $this
        ->drupalSetMessage($this
        ->t('Facebook login is disabled for site administrator. Login with your local user account.'), 'error');
      return FALSE;
    }

    // Prevent login if user has one of the roles defined in module settings.
    if ($this
      ->loginDisabledByRole($drupal_user)) {
      $this
        ->drupalSetMessage($this
        ->t('Facebook login is disabled for your role. Please login with your local user account.'), 'error');
      return FALSE;
    }

    // Check that the account is active and log the user in.
    if ($drupal_user
      ->isActive()) {
      $this
        ->userLoginFinalize($drupal_user);

      // Dispatch an event so that other modules can react to the user login.
      // Set the account twice on the event: as the main subject but also in the
      // list of arguments.
      $event = new GenericEvent($drupal_user, [
        'account' => $drupal_user,
      ]);
      $this->eventDispatcher
        ->dispatch('simple_fb_connect.user_login', $event);

      // TODO: Add Boost cookie if Boost module is enabled
      // https://www.drupal.org/node/2524372
      $this
        ->drupalSetMessage($this
        ->t('You are now logged in as @username.', array(
        '@username' => $drupal_user
          ->getAccountName(),
      )));
      return TRUE;
    }

    // If we are still here, account is blocked.
    $this
      ->drupalSetMessage($this
      ->t('You could not be logged in because your user account @username is not active.', array(
      '@username' => $drupal_user
        ->getAccountName(),
    )), 'warning');
    $this->loggerFactory
      ->get('simple_fb_connect')
      ->warning('Facebook login for user @user prevented. Account is blocked.', array(
      '@user' => $drupal_user
        ->getAccountName(),
    ));
    return FALSE;
  }

  /**
   * Checks if user registration is blocked in Drupal account settings.
   *
   * @return bool
   *   True if registration is blocked
   *   False if registration is not blocked
   */
  protected function registrationBlocked() {

    // Check if Drupal account registration settings is Administrators only.
    if ($this->configFactory
      ->get('user.settings')
      ->get('register') == 'admin_only') {
      return TRUE;
    }

    // If we didnt' return TRUE already, registration is not blocked.
    return FALSE;
  }

  /**
   * Ensures that Drupal usernames will be unique.
   *
   * Drupal usernames will be generated so that the user's full name on Facebook
   * 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 $fb_name
   *   User's full name on Facebook.
   */
  protected function generateUniqueUsername($fb_name) {
    $base = trim($fb_name);
    $i = 1;
    $candidate = $base;
    while ($this
      ->loadUserByProperty('name', $candidate)) {
      $i++;
      $candidate = $base . " " . $i;
    }
    return $candidate;
  }

  /**
   * Returns the status for new users.
   *
   * @return int
   *   Value 0 means that new accounts remain blocked and require approval.
   *   Value 1 means that visitors can register new accounts without approval.
   */
  protected function getNewUserStatus() {
    if ($this->configFactory
      ->get('user.settings')
      ->get('register') == 'visitors') {
      return 1;
    }
    return 0;
  }

  /**
   * Checks if current user is admin and admin login via FB is disabled.
   *
   * @param \Drupal\user\Entity\User $drupal_user
   *   User object.
   *
   * @return bool
   *   True if current user is admin and admin login via fB is disabled.
   *   False otherwise.
   */
  protected function loginDisabledForAdmin(User $drupal_user) {

    // Check if current user is admin.
    if ($drupal_user
      ->id() == 1) {

      // Check if admin FB login is disabled.
      if ($this->configFactory
        ->get('simple_fb_connect.settings')
        ->get('disable_admin_login')) {
        $this->loggerFactory
          ->get('simple_fb_connect')
          ->warning('Facebook login for user @user prevented. Facebook login for site administrator (user 1) is disabled in module settings.', array(
          '@user' => $drupal_user
            ->getAccountName(),
        ));
        return TRUE;
      }
    }

    // User is not admin or admin login is not disabled.
    return FALSE;
  }

  /**
   * Checks if the user has one of the "FB login disabled" roles.
   *
   * @param \Drupal\user\Entity\User $drupal_user
   *   User object.
   *
   * @return bool
   *   True if login is disabled for one of this user's role
   *   False if login is not disabled for this user's roles
   */
  protected function loginDisabledByRole(User $drupal_user) {

    // Read roles that are blocked from module settings.
    $disabled_roles = $this->configFactory
      ->get('simple_fb_connect.settings')
      ->get('disabled_roles');

    // Loop through all roles the user has.
    foreach ($drupal_user
      ->getRoles() as $role) {

      // Check if FB login is disabled for this role.
      // Disabled roles have non-zero value.
      if (array_key_exists($role, $disabled_roles) && $disabled_roles[$role] !== 0) {
        $this->loggerFactory
          ->get('simple_fb_connect')
          ->warning('Facebook login for user @user prevented. Facebook login for role @role is disabled in module settings.', array(
          '@user' => $drupal_user
            ->getAccountName(),
          '@role' => $role,
        ));
        return TRUE;
      }
    }

    // FB login is not disabled for any of the user's roles.
    return FALSE;
  }

  /**
   * Downloads and sets user profile picture.
   *
   * @param User $drupal_user
   *   User object to update the profile picture for.
   * @param string $picture_url
   *   Absolute URL where the picture will be downloaded from.
   * @param string $fbid
   *   User's Facebook ID.
   *
   * @return bool
   *   True if picture was successfully set.
   *   False otherwise.
   */
  public function setProfilePic(User $drupal_user, $picture_url, $fbid) {

    // Try to download the profile picture and add it to user fields.
    if ($this
      ->userPictureEnabled()) {
      if ($file = $this
        ->downloadProfilePic($picture_url, $fbid)) {
        $drupal_user
          ->set('user_picture', $file
          ->id());
        $drupal_user
          ->save();
        return TRUE;
      }
    }
    return FALSE;
  }

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

    // Make sure that we have everything we need.
    if (!$picture_url || !$fbid) {
      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
      ->filePrepareDirectory($directory, 1)) {
      $this->loggerFactory
        ->get('simple_fb_connect')
        ->error('Could not save FB profile picture. Directory is not writeable: @directory', array(
        '@directory' => $directory,
      ));
      return FALSE;
    }

    // Generate filename and transliterate. FB API always serves JPG.
    $filename = $this->transliteration
      ->transliterate($fbid . '.jpg', 'en', '_', 50);
    $destination = $directory . '/' . $filename;

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

  /**
   * Returns whether this site supports the default user picture feature.
   *
   * We use this method instead of the procedural user_pictures_enabled()
   * so that we can unit test our own methods.
   *
   * @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 file_prepare_directory.
   *
   * 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 file_prepare_directory
   */
  protected function filePrepareDirectory(&$directory, $options) {
    return file_prepare_directory($directory, $options);
  }

  /**
   * 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);
  }

  /**
   * Wrapper for drupal_set_message.
   *
   * 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 drupal_set_message
   */
  protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
    return drupal_set_message($message, $type, $repeat);
  }

  /**
   * 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.
   *
   * @see user_password
   */
  protected function userPassword($length) {
    return user_password($length);
  }

  /**
   * Wrapper for user_login_finalize.
   *
   * 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 user_password
   */
  protected function userLoginFinalize(UserInterface $account) {
    return user_login_finalize($account);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
SimpleFbConnectUserManager::$configFactory protected property
SimpleFbConnectUserManager::$entityFieldManager protected property
SimpleFbConnectUserManager::$entityTypeManager protected property
SimpleFbConnectUserManager::$eventDispatcher protected property
SimpleFbConnectUserManager::$loggerFactory protected property
SimpleFbConnectUserManager::$token protected property
SimpleFbConnectUserManager::$transliteration protected property
SimpleFbConnectUserManager::createUser public function Create a new user account.
SimpleFbConnectUserManager::downloadProfilePic protected function Downloads the profile picture to Drupal filesystem.
SimpleFbConnectUserManager::drupalSetMessage protected function Wrapper for drupal_set_message.
SimpleFbConnectUserManager::filePrepareDirectory protected function Wrapper for file_prepare_directory. 1
SimpleFbConnectUserManager::generateUniqueUsername protected function Ensures that Drupal usernames will be unique.
SimpleFbConnectUserManager::getNewUserStatus protected function Returns the status for new users.
SimpleFbConnectUserManager::getPictureDirectory protected function Returns picture directory if site supports the user picture feature.
SimpleFbConnectUserManager::loadUserByProperty public function Loads existing Drupal user object by given property and value.
SimpleFbConnectUserManager::loginDisabledByRole protected function Checks if the user has one of the "FB login disabled" roles.
SimpleFbConnectUserManager::loginDisabledForAdmin protected function Checks if current user is admin and admin login via FB is disabled.
SimpleFbConnectUserManager::loginUser public function Logs the user in.
SimpleFbConnectUserManager::registrationBlocked protected function Checks if user registration is blocked in Drupal account settings.
SimpleFbConnectUserManager::setProfilePic public function Downloads and sets user profile picture.
SimpleFbConnectUserManager::systemRetrieveFile protected function Wrapper for system_retrieve_file. 1
SimpleFbConnectUserManager::userLoginFinalize protected function Wrapper for user_login_finalize. 1
SimpleFbConnectUserManager::userPassword protected function Wrapper for user_password. 1
SimpleFbConnectUserManager::userPictureEnabled protected function Returns whether this site supports the default user picture feature.
SimpleFbConnectUserManager::__construct public function Constructor.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.