<?php

/**
 * Implements hook_menu()
 */
function groups_oauth2_menu() {
  $items['oauth2/response'] = array(
    'page callback' => 'groups_oauth2_response_handler',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/services/oauth2'] = array(
    'title' => 'Oauth2 Settings',
    'description' => 'OpenStackID Oauth2 settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('groups_oauth2_admin_settings'),
    'access arguments' => array('administer site configuration'),
    'file' => 'groups_oauth2.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_form_alter()
 *
 * Add Login with OpenStackID button to login form.
 */
function groups_oauth2_form_user_login_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_login' || $form_id == 'user_login_block') {
    $form['submit_groups'] = array(
      '#type' => 'submit',
      '#value' => t('Login with OpenStackID'),
      '#submit' => array('groups_oauth2_user_login_submit'),
      '#limit_validation_errors' => array(),
      '#weight' => 1000,
    );
  }
}

/**
 * Handle login form submit.
 *
 * Redirect to oauth2 site and request an authorization code.
 */
function groups_oauth2_user_login_submit() {
  if (!_groups_oauth2_validate_provider_settings()) {
    return FALSE;
  }
  if (isset($_GET['destination'])) {
    unset($_GET['destination']);
  }
  $redirect_uri = url('oauth2/response', array('https' => $GLOBALS['is_https'], 'absolute' => TRUE));
  $url = _groups_oauth2_authorize_url('code', $redirect_uri, 'auto', 'profile email');
  drupal_goto($url);
}

/**
 * Oauth2 response handler callback.
 *
 * Process to authorization code, invoke token and login or create
 * a new user.
 */
function groups_oauth2_response_handler() {
  if (isset($_GET['code'])) {
    if (!_groups_oauth2_validate_provider_settings()) {
     return FALSE;
    }
    $redirect_uri = url('oauth2/response', array('https' => $GLOBALS['is_https'], 'absolute' => TRUE));
    $response = _groups_oauth2_request_token('authorization_code', $_GET['code'], $redirect_uri);
    if ($response->code == 200) {
      $data = json_decode($response->data);
      $userinfo = groups_oauth2_get_userinfo($data->access_token);
      if (!$userinfo) {
         drupal_set_message(t("Failed to retrieve user information"), 'error');
         drupal_goto();
         return;
      }
      $username = check_plain(strtolower($userinfo->name));
      $query = "SELECT uid FROM {users} WHERE name = :name";
      $uid = db_query($query, array(':name' => $username))->fetchField();
      $i = 0;
      while ($uid) {
        $i++;
        $uid = db_query($query, array(':name' => ($username . $i)))->fetchField();
      }
      if ($i > 0) {
        $username = $username . $i;
      }
      $account = groups_oauth2_find_existing_user($userinfo);
      if (!$account) {
        $edit = array(
          'name' => $username,
          'mail' => $userinfo->email,
          'init' => $userinfo->email,
          'status' => 1,
          'timezone' => variable_get('date_default_timezone'),
          'data' => array(
            'oauth2_fullname' => check_plain($userinfo->name),
          ),
        );
        $account = user_save(NULL, $edit);
        // Allow other modules to manipulate the user information after save.
        foreach (module_implements('oauth2_user_save') as $module) {
          $function = $module . '_oauth2_user_save';
          $function($account, $userinfo);
        }
      } else {
        // resave user profile if oauth2_picture_ttl expired
        $oauth2_picture_ttl = isset($account->data['oauth2_picture_ttl']) ?
          $account->data['oauth2_picture_ttl'] : 0;
        if (time() >= $oauth2_picture_ttl) {
          // Allow other modules to manipulate the user information after save.
          foreach (module_implements('oauth2_user_save') as $module) {
            $function = $module . '_oauth2_user_save';
            $function($account, $userinfo);
          }
        }
      }
      $form_state['uid'] = $account->uid;
      user_login_submit(array(), $form_state);
      drupal_goto('user/' . $account->uid);
    } else {
      $data = json_decode($response->data);
      drupal_set_message(t("The account is not authenticated. Reason: @error",
        array('@error' => is_object($data) ? $data->error : 'Not known.')), 'error');
    }
  }
  drupal_goto();
}

/**
 * Fetch user info from oauth2 server.
 */
function groups_oauth2_get_userinfo($access_token) {
  $query = array('access_token' => $access_token);
  $request_url = url(variable_get('groups_oauth2_provider').'/api/v1/users/me', array('query' => $query));
  $response = drupal_http_request($request_url);
  if ($response->code == 200) {
    return json_decode($response->data);
  }
  return FALSE;
}

/**
 * Find a Drupal user by oauth2 user info.
 */
function groups_oauth2_find_existing_user($userinfo) {
  if ($user = user_load_by_mail($userinfo->email)) {
    return $user;
  }
  return FALSE;
}

/**
 * Ensure groups oauth2 authentication variables.
 *
 * @return boolean
 *   Return TRUE if groups_oauth2_provider, groups_oauth2_client_id and
 *   groups_oauth2_client_secret are set, otherwise throw an error message.
 */
function _groups_oauth2_validate_provider_settings() {
  $provider_url = variable_get('groups_oauth2_provider');
  $client_id = variable_get('groups_oauth2_client_id');
  $client_secret = variable_get('groups_oauth2_client_secret');
  if ((!isset($provider_url)) || (!isset($client_id)) || (!isset($client_secret))) {
    drupal_set_message(t('Groups oauth2 provider not properly configured.'), 'warning');
    return FALSE;
  }
  return TRUE;
}

/**
 * Build oauth2 authorization url.
 *
 * @param  string $response_type
 *   Oauth2 response type, use code for basic flow
 * @param  string $redirect_uri
 *   Determines where the response will be sent
 * @param  string $approval_prompt
 *   Approval prompt, set to auto
 * @param  string $scope
 *   Aouth2 scope requesting access for
 * @return string
 *   The full url for authorization code request.
 */
function _groups_oauth2_authorize_url($response_type, $redirect_uri, $approval_prompt, $scope) {
  $params = array(
    'response_type' => $response_type,
    'client_id' => variable_get('groups_oauth2_client_id'),
    'redirect_uri' => $redirect_uri,
    'approval_prompt' => $approval_prompt,
    'scope' => $scope,
  );
  $provider_url = variable_get('groups_oauth2_provider');
  return $provider_url.'/oauth2/auth?'.http_build_query($params);
}

/**
 * Send a token request based on authorization code to oauth2 provider.
 *
 * @param  string $grant_type
 *   Set to 'authorization_code'
 * @param  string $code
 *   Code received from authorization request.
 * @param  string $redirect_uri
 *   The redirect uri set for this application.
 * @return [type]
 *   Http response of token request.
 */
function _groups_oauth2_request_token($grant_type, $code, $redirect_uri) {
  $params = array(
    'grant_type' => $grant_type,
    'code' => $code,
    'redirect_uri' => $redirect_uri,
    'client_id' => variable_get('groups_oauth2_client_id'),
    'client_secret' => variable_get('groups_oauth2_client_secret'),
  );
  $elements = array();
  foreach ($params as $k => $v) {
    $elements[] = "$k=$v";
  }
  $data = implode('&', $elements);
  $options = array(
    'method' => 'POST',
    'data' => $data,
    'timeout' => 15,
    'headers' => array('Content-Type' => 'application/x-www-form-urlencoded'),
  );
  $provider_url = variable_get('groups_oauth2_provider');
  return drupal_http_request($provider_url.'/oauth2/token', $options);
}

/**
 * Return the profile picture filename from url.
 */
function _groups_oauth2_get_profile_picture_filename($url) {
  $parts = parse_url($url);
  $element = explode('/', $parts['path']);
  $basename = end($element);
  return 'public://profile-images/'.$basename;
}

/**
 * Implements hook_theme().
 */
function groups_oauth2_theme() {
  return array(
    'groups_oauth2_user_picture_override' => array(
      'variables' => array(
        'account' => NULL,
        'picture_url' => NULL,
      ),
    ),
  );
}

/**
 * Implements hook_theme_registry_alter(), override
 * @param  [type] $theme_registry [description]
 * @return [type]                 [description]
 */
function groups_oauth2_theme_registry_alter(&$theme_registry) {
  // Re-register the original theme function under a new name.
  $theme_registry['groups_oauth2_user_picture_orig'] = $theme_registry['user_picture'];
  // Override theme username
  $theme_registry['user_picture'] = array(
    'arguments' => array('account' => NULL),
    'function' => 'groups_oauth2_theme_user_picture_override',
    'preprocess functions' => array(),
    'theme path' => drupal_get_path('module', 'groups_oauth2'),
  );
}

/**
 * Make rendering of Oauth2 user picture themeable
 */
function theme_groups_oauth2_user_picture_override($variables) {
  $picture_url = $variables['picture_url'];
  $account = $variables['account'];
  $image = theme('image', array('path' => $picture_url, 'alt' => $account->name));
  return '<div class="picture">' . $image . '</div>';
}

/**
 * Override user picture theme, and render oauth2 picture if available.
 */
function groups_oauth2_theme_user_picture_override($variables) {
  $account = $variables['account'];
  if (isset($account->data['oauth2_picture'])) {
    if (variable_get('oauth2_fetch_profile_picture', FALSE)) {
      $image_path = _groups_oauth2_get_profile_picture_filename($account->data['oauth2_picture']);
      $picture_url = file_create_url($image_path);
    } else {
      $picture_url = $account->data['oauth2_picture'];
    }
    $output = theme('groups_oauth2_user_picture_override', array('account' => $account, 'picture_url' => $picture_url));
  } else {
    $output =  theme('groups_oauth2_user_picture_orig', array('account' => $account));
  }
  return $output;
}

/**
 * Implements hook_username_alter().
 *
 * @see realname_username_alter()
 */
function groups_oauth2_username_alter(&$name, $account) {
  static $in_username_alter = FALSE;
  if (empty($account->uid)) {
    // Don't alter anonymous users or objects that do not have any user ID.
    return;
  }
  if (isset($account->data['oauth2_fullname'])) {
    $name = $account->data['oauth2_fullname'];
    return;
  }
  // Real name was not yet available for the account so we need to generate it.
  // Because tokens may call format_username() we need to prevent recursion.
  if (!$in_username_alter) {
    $in_username_alter = TRUE;
    // If $account->realname was undefined, then the user account object was
    // not properly loaded. We must enforce calling user_load().
    if ($oauth2_account = user_load($account->uid)) {
      groups_oauth2_username_alter($name, $oauth2_account);
    }
    $in_username_alter = FALSE;
  }
}
