csna.module 9.12 KB
<?php

/**
 * @file
 * The Chinese Social Networks Authentication (CSNA) module provides a global
 * framework and API to allow other modules to define specific authentication
 * methods from Chinese Social Networks through a supported OAuth protocol.
 */

define('CSNA_OAUTH2', 'oauth2');

/**
 * Implements hook_menu().
 */
function csna_menu() {
  $items['csna/%csna_provider'] = array(
    'title' => 'CSNA Redirect',
    'page callback' => 'csna_redirect',
    'page arguments' => array(1),
    'access callback' => 'user_is_anonymous',
    'type' => MENU_CALLBACK,
  );

  $items['csna/%csna_provider/callback'] = array(
    'title' => 'Authentication callback',
    'page callback' => 'csna_callback',
    'page arguments' => array(1),
    'access callback' => 'user_is_anonymous',
    'type' => MENU_CALLBACK,
  );

  $items['csna/%user/complete-register'] = array(
    'title' => t('Complete register'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('csna_complete_register_form', 1),
    'access callback' => 'csna_user_edit_access',
    'access arguments' => array(1),
    'file' => 'csna.pages.inc',
  );

  $items['admin/config/services/csna'] = array(
    'title' => 'Chinese Social Networks Authentication settings (CSNA)',
    'description' => 'Configure the CSNA key or secrect code settings for third party authentication API providers.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('csna_admin_settings'),
    'access arguments' => array('administer csna'),
    'file' => 'csna.admin.inc',
  );

  return $items;
}

/**
 * Check the Provider ID.
 *
 * @param string $provider_id
 *   The Provider ID.
 *
 * @return mixed
 *   A value of $providers[$provider_id] from csna_providers(),
 *   or FALSE if the value does not exist.
 */
function csna_provider_load($provider_id) {
  $providers = csna_providers();
  return isset($providers[$provider_id]) ? $providers[$provider_id] : FALSE;
}

/**
 * Implements hook_perm().
 */
function csna_permission() {
  return array(
    'administer csna' => array(
      'title' => t('Administer CSNA'),
      'description' => t('Perform administration tasks for CSNA.'),
    ),
  );
}

/**
 * Implements hook_theme().
 */
function csna_theme($existing, $type, $theme, $path) {
  $providers = csna_providers();
  return array(
    'csna_providers' => array(
      'variables' => array('providers' => $providers),
    ),
  );
}

/**
 * Menu callback: CSNA Redirect.
 */
function csna_redirect($provider) {
  $parameters = array(
    'client_id' => $provider['key'],
    'redirect_uri' => $provider['callback'],
    'response_type' => 'code',
  );
  if ($provider['provider_id'] == 'wechat') {
    unset($parameters['client_id']);
    $parameters['appid'] = $provider['key'];
    $parameters['scope'] = 'snsapi_login';
  }
  drupal_goto($provider['authorize_uri'], array('query' => $parameters));
}

/**
 * Menu callback: CSNA Callback.
 */
function csna_callback($provider) {

  global $user;
  $data = module_invoke($provider['module'], 'csna_provider_callback', $provider, $_REQUEST);

  // Forcing array format because of Sina weibo will return an Object.
  if (!is_array($data)) {
    $data = (array) $data;
  }

  if (isset($data['access_token'])) {
    $identity = $provider['provider_id'] . '_' . $data['uid'];
    $account = user_external_load($identity);

    if (!isset($account->uid)) {
      $name = $data['name'];
      $ac = user_load_by_name($name);
      if ($ac) {
        $separator = '_';
        $i = 0;
        do {
          $i++;
          $name = $data['name'] . $separator . $i;
        } while (user_load_by_name($name));
      }
      //User avatar
      if (isset($data['avatar_link'])) {
        $avatar_directory = file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures');
        $avatar = system_retrieve_file($data['avatar_link'], $avatar_directory, TRUE, FILE_EXISTS_RENAME);
      }
      $edit = array(
        'name' => $name,
        'status' => 1,
        'picture' =>  isset($avatar->fid) ? $avatar->fid : '',
      );
      $account = user_save(NULL, $edit);
      user_set_authmaps($account, array('authname_csna' => $identity));
    }

    csna_execute_user_login($account);

    if (variable_get('csna_require_email', TRUE) && empty($account->mail)) {
      drupal_goto('csna/' . $account->uid . '/complete-register');
    }
  }
  drupal_goto('user');
}

/**
 * Helper function for CSNA Callback.
 *
 * It executes user login after returning from provider's authentication page.
 * If no valid account is found or authentication with providers failed, this
 * function returns FALSE.
 */
function csna_execute_user_login($account) {
  if (empty($account) OR !isset($account->uid)) {
    return FALSE;
  }
  $form_state['uid'] = $account->uid;
  user_login_submit(array(), $form_state);
  return TRUE;
}

/**
 * Implements hook_form_alter().
 */
function csna_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_login' || $form_id == 'user_login_block' || $form_id == 'user_register_form') {
    $providers = csna_providers();

    // if admin form
    if (empty($form['administer_users']['#value'])) {
      if (isset($form['submitted']['user_login_block']['#suffix']) && !empty($form['submitted']['user_login_block']['#suffix'])) {
        $form['submitted']['user_login_block']['#suffix'] .= theme('csna_providers', array('providers' => $providers));
      }
      else {
        $form['submitted']['user_login_block']['#suffix'] = theme('csna_providers', array('providers' => $providers));
      }
    }
  }
}

/**
 * Returns an array of OAuth Third Party authentication providers.
 *
 * CSNA's API Framework provides a method for any enabled module to define
 * additional authentication providers. This function gathers information on
 * provider authentication methods defined by enabled modules.
 *
 * @return array
 *   An associative array of providers keyed by the provider_id.
 *
 * @see hook_csna_provider_info()
 */
function csna_providers($valid = FALSE) {
  static $providers;

  if (!isset($providers)) {
    $providers = array();

    // Build the providers array, including module names for the purpose
    // of including files if necessary.
    // An associative array of providers objects keyed by the provider_id.
    foreach (module_implements('csna_provider_info') as $module) {
      foreach (module_invoke($module, 'csna_provider_info') as $provider_id => $provider) {
        $provider['provider_id'] = $provider_id;
        $provider['module'] = $module;
        $provider['key'] = variable_get('csna_' . $provider_id . '_key', '');
        $provider['secret'] = variable_get('csna_' . $provider_id . '_secret', '');
        $providers[$provider_id] = $provider;
        if ($valid && (empty($provider['key']) || empty($provider['secret']))) {
          unset($providers[$provider_id]);
        }
      }
    }

    drupal_alter('csna_provider_info', $providers);

    foreach ($providers as $provider_id => & $provider) {
      $defaults = array(
        'provider_id' => $provider_id,
        'base' => $provider_id,
        'title' => '',
        'description' => '',
        'active' => FALSE,
        'callback' => url('csna/' . $provider_id . '/callback', array('absolute' => TRUE)),
        'file' => '',
      );

      $provider += $defaults;

      // Default the display title to the title if necessary.  The display
      // title is used in instances where the login method has an official
      // name but a different method of display on selection forms (like some
      // text and a set of images).
      if (empty($provider['display_title'])) {
        $provider['display_title'] = $provider['title'];
      }

      // Default the short title to the title if necessary.  Like the display
      // title, the short title is an alternate way of displaying the title to
      // the user consisting of plain text but with unnecessary information
      // stripped off.
      if (empty($provider[$provider_id]['short_title'])) {
        $provider['short_title'] = $provider['title'];
      }
    }
  }

  return $providers;
}

/**
 * Render a themed list of links to providers authorized login URL pages.
 *
 * @param array $providers
 *   An associative array of providers keyed by the provider_id, as returned by
 *   the function csna_providers().
 *   Mostly the display_title property of providers is used in this function
 *   to display a custom title, name or even HTML to be linked to corresponding
 *   provider's authorized login URL page. See API documentation for more
 *   information on providers available properties.
 *
 * @see csna_providers()
 * @see hook_csna_provider_info()
 */
function theme_csna_providers($vars) {
  $providers = $vars['providers'];
  $items = array();
  foreach ($providers as $provider_id => $provider) {
    $items[] = array(
      'data' => l($provider['display_title'], 'csna/' . $provider_id, array('html' => TRUE)),
      'id' => 'csna_provider_' . $provider_id,
    );
  }

  return theme('item_list', array('items' => $items, 'attributes' => array('class' => 'csna-items')));
}

/**
 * Access callback for CSNA complete register.
 *
 * @param $account
 */
function csna_user_edit_access($account) {
  return $GLOBALS['user']->uid == $account->uid;
}