<?php
/**
 * @file
 * The module file.
 */

define('FEEDS_OAUTH_LIBRARY_PATH_DEFAULT', 'sites/all/libraries/php-proauth');

/**
 * Implements hook_feed_plugins().
 */
function feeds_oauth_feeds_plugins() {
  $info = array();
  $info['OAuthHTTPFetcher'] = array(
    'name' => 'HTTP OAuth fetcher',
    'description' => 'Download content with OAuth authentication.',
    'help' => 'Uses OAuth to authenticate requests to remote resources.',
    'handler' => array(
      // This is the key name, not the class name.
      'parent' => 'FeedsHTTPFetcher',
      'class' => 'OAuthHTTPFetcher',
      'file' => 'OAuthHTTPFetcher.inc',
      'path' => drupal_get_path('module', 'feeds_oauth'),
    ),
  );
  $info['OAuth2HTTPSFetcher'] = array(
    'name' => 'HTTPS OAuth 2.0 fetcher',
    'description' => 'Download content with OAuth 2.0 authentication.',
    'help' => 'Uses OAuth 2.0 to authenticate requests to remote resources.',
    'handler' => array(
      // This is the key name, not the class name.
      'parent' => 'OAuthHTTPFetcher',
      'class' => 'OAuth2HTTPSFetcher',
      'file' => 'OAuth2HTTPSFetcher.inc',
      'path' => drupal_get_path('module', 'feeds_oauth'),
    ),
  );
  return $info;
}

/**
 * Implements hook_menu().
 */
function feeds_oauth_menu() {
  // For OAuth 1.0
  $items['feeds/oauth/authenticate/%'] = array(
    'title' => 'OAuth authentication',
    'type' => MENU_CALLBACK,
    'page callback' => 'feeds_oauth_authenticate',
    'page arguments' => array(3),
    'access callback' => TRUE,
  );
  $items['feeds/oauth/callback/%'] = array(
    'title' => 'OAuth callback',
    'type' => MENU_CALLBACK,
    'page callback' => 'feeds_oauth_callback',
    'page arguments' => array(3),
    'access callback' => TRUE,
  );

  // For OAuth 2.0
  $items['feeds/oauth2/authenticate/%'] = array(
    'title' => 'OAuth2 authentication',
    'type' => MENU_CALLBACK,
    'page callback' => 'feeds_oauth_authenticate2',
    'page arguments' => array(3),
    'access callback' => TRUE,
  );
  $items['feeds/oauth2/callback/%'] = array(
    'title' => 'OAuth2 callback',
    'type' => MENU_CALLBACK,
    'page callback' => 'feeds_oauth_callback2',
    'page arguments' => array(3),
    'access callback' => TRUE,
  );

  // Admin
  $items['admin/config/services/feeds-oauth'] = array(
    'title' => 'Feeds OAuth',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('feeds_oauth_admin'),
    'access arguments' => array('administer site configuration'),
  );

  return $items;
}

/**
 * Form function for `feeds_oauth_admin`.
 */
function feeds_oauth_admin($form, $form_state) {
  $form['feeds_oauth_library_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Library path'),
    '#description' => t('Path for the <a href="@url">php-proauth</a> library.', array('@url' => 'https://code.google.com/p/php-proauth/')),
    '#default_value' => variable_get('feeds_oauth_library_path', FEEDS_OAUTH_LIBRARY_PATH_DEFAULT),
    '#field_prefix' => DRUPAL_ROOT . '/',
  );
  $form['#validate'][] = '_feeds_oauth_admin_validate';
  return system_settings_form($form);
}

/**
 * Validate function for `feeds_oauth_admin`.
 */
function _feeds_oauth_admin_validate($form, $form_state) {
  $path = rtrim($form_state['values']['feeds_oauth_library_path'], '/');
  if (!is_dir($path) || !is_file($path . '/lib/oauth/OAuthClient.php')) {
    form_set_error('feeds_oauth_library_path', t('The php-proauth library path you entered does not point to a valid location. Please enter a valid full path.'));
    return;
  }
}

/**
 * Store the access token in our {feeds_oauth_access_tokens} table.
 */
function _feeds_oauth_store_token($token, $uid, $site_id) {
  $timestamp = time();
  $data = array(
    'uid' => $uid,
    'site_id' => $site_id,
    'oauth_token' => $token['access_token'],
    'oauth_token_secret' => isset($token['access_token_secret']) ? $token['access_token_secret'] : '',
    'timestamp' => $timestamp,
    'expires' => isset($token['expires_in']) ? $timestamp + $token['expires_in'] : (isset($token['expires']) ? $timestamp + $token['expires'] : 0),
    'oauth_refresh_token' => isset($token['refresh_token']) ? $token['refresh_token'] : '',
  );
  db_merge('feeds_oauth_access_tokens')
    ->key(array('uid' => $uid, 'site_id' => $site_id))
    ->fields($data)
    ->execute();
}

/**
 * Menu callback to start authenticating OAuth 1.
 */
function feeds_oauth_authenticate($id) {
  $path = variable_get('feeds_oauth_library_path', FEEDS_OAUTH_LIBRARY_PATH_DEFAULT);
  require_once $path . '/lib/oauth/OAuthClient.php';

  $fetcher = feeds_importer($id)->fetcher;
  $config = $fetcher->getConfig();

  $oauth = new proauth\OAuthCurlClient(
    new proauth\OAuthConsumer($config['consumer_key'], $config['consumer_secret']),
    new proauth\OAuthSignatureHMACSHA1(),
    NULL
  );
  try {
    $request_token = $oauth->_getTempToken($config['request_token_url'], array(), TRUE);
  }
  catch (Exception $e) {
    drupal_set_message($e->getMessage(), 'error');
    drupal_goto($_SERVER['HTTP_REFERER']);
  }
  $_SESSION['feeds'] = array(
    'request_token' => serialize($request_token),
    'id' => $id,
    'destination' => $_SERVER['HTTP_REFERER'],
  );
  drupal_goto(url($config['authorize_url'], array('absolute' => TRUE, 'query' => array('oauth_token' => $request_token->getToken()))));
}

/**
 * Menu callback to complete authenticating OAuth 1.
 */
function feeds_oauth_callback($site_id) {
  $path = variable_get('feeds_oauth_library_path', FEEDS_OAUTH_LIBRARY_PATH_DEFAULT);
  require_once $path . '/lib/oauth/OAuthClient.php';

  $fetcher = feeds_importer($_SESSION['feeds']['id'])->fetcher;
  $config = $fetcher->getConfig();
  $request_token = unserialize($_SESSION['feeds']['request_token']);
  $destination = $_SESSION['feeds']['destination'];
  unset($_SESSION['feeds']);

  $oauth = new proauth\OAuthCurlClient(
    new proauth\OAuthConsumer($config['consumer_key'], $config['consumer_secret']),
    new proauth\OAuthSignatureHMACSHA1(),
    $request_token
  );

  if (empty($_GET['oauth_token']) || empty($_GET['oauth_verifier']) || $_GET['oauth_token'] != $request_token->getToken()) {
    drupal_set_message(t('Invalid OAuth token.'), 'error');
    drupal_goto($destination);
  }

  try {
    $access_token = $oauth->_getAccessToken($config['access_token_url'], array('oauth_verifier' => $_GET['oauth_verifier']), NULL, TRUE);
  }
  catch (Exception $e) {
    drupal_set_message($e->getMessage(), 'error');
    drupal_goto($destination);
  }
  global $user;
  _feeds_oauth_store_token(array('access_token' => $access_token->getToken(), 'access_token_secret' => $access_token->getSecret()), $user->uid, $site_id);
  drupal_goto($destination);
}

/**
 * Menu callback to start authenticating OAuth 2.
 */
function feeds_oauth_authenticate2($id) {
  $fetcher = feeds_importer($id)->fetcher;
  $config = $fetcher->getConfig();

  $query = array(
    'client_id' => $config['consumer_key'],
    'redirect_uri' => url('feeds/oauth2/callback/' . $id, array('absolute' => TRUE)),
    'scope' => $config['scope'],
    'response_type' => 'code',
    'access_type' => 'offline',
  );

  $_SESSION['feeds'] = array(
    'destination' => $_SERVER['HTTP_REFERER'],
  );

  drupal_goto(url($config['authorize_url'], array('absolute' => TRUE, 'query' => $query)));
}

/**
 * Menu callback to complete authenticating OAuth 2.
 */
function feeds_oauth_callback2($id) {
  $fetcher = feeds_importer($id)->fetcher;
  $config = $fetcher->getConfig();

  $code = isset($_GET['code']) ? $_GET['code'] : FALSE;
  $token = FALSE;
  $destination = $_SESSION['feeds']['destination'];
  unset($_SESSION['feeds']);

  // TODO: Rewrite this using OAuth2CurlClient.
  if ($code) {
    $query = array(
      'code' => $code,
      'client_id' => $config['consumer_key'],
      'client_secret' => $config['consumer_secret'],
      'redirect_uri' => url('feeds/oauth2/callback/' . $id, array('absolute' => TRUE)),
      'grant_type' => 'authorization_code',
    );
    $response = drupal_http_request($config['access_token_url'], array(
      'method' => 'POST',
      'headers' => array('Content-Type' => 'application/x-www-form-urlencoded'),
      'data' => http_build_query($query, '', '&'),
    ));
    if ($response->code == 200) {
      $token = drupal_json_decode($response->data);
      if (empty($token)) {
        parse_str($response->data, $token);
      }
    }
    else {
      drupal_set_message(t('Attempt to retrieve access token failed: %response', array('%response' => print_r($response, TRUE))), 'error');
    }
  }
  else {
    drupal_set_message(t('OAuth 2 callback did not receive %code argument.', array('%code' => 'code')), 'error');
  }

  if ($token) {
    global $user;
    _feeds_oauth_store_token($token, $user->uid, $config['site_id']);
  }

  drupal_goto($destination);
}

/**
 * Menu callback to refresh access tokens for OAuth 2.
 */
function feeds_oauth_refresh2($refresh_token, $uid, $id) {
  $fetcher = feeds_importer($id)->fetcher;
  $config = $fetcher->getConfig();

  $query = array(
    'client_id' => $config['consumer_key'],
    'client_secret' => $config['consumer_secret'],
    'refresh_token' => $refresh_token,
    'grant_type' => 'refresh_token',
  );

  $response = drupal_http_request($config['access_token_url'], array(
    'method' => 'POST',
    'headers' => array('Content-Type' => 'application/x-www-form-urlencoded'),
    'data' => http_build_query($query, '', '&'),
  ));

  $token = NULL;
  if ($response->code == 200) {
    $token = drupal_json_decode($response->data);

    // Pass on the old refresh token if we didn't get a new one.
    if (empty($token['refresh_token'])) {
      $token['refresh_token'] = $refresh_token;
    }

    _feeds_oauth_store_token($token, $uid, $config['site_id']);
  }
  else {
    watchdog('feeds_oauth', 'Attempt to refresh token failed: %response', array('%response' => print_r($response, TRUE)), WATCHDOG_ERROR);
  }

  return $token;
}

/**
 * Implements hook_feeds_oauth_authenticator().
 */
function feeds_oauth_feeds_oauth_authenticator() {
  return array('feeds_oauth_get_tokens' => 'Feeds OAuth');
}

/**
 * Get tokens from database.
 */
function feeds_oauth_get_tokens($uid, $site_id, $id) {
  $token = db_query("SELECT * FROM {feeds_oauth_access_tokens} WHERE uid = :uid AND site_id = :site_id", array(':uid' => $uid, ':site_id' => $site_id))->fetchAssoc();

  if ($token['expires'] && time() > $token['expires']) {
    // Remove the expired key.
    db_delete('feeds_oauth_access_tokens')
      ->condition('uid', $uid)
      ->condition('site_id', $site_id)
      ->execute();

    if (!empty($token['oauth_refresh_token'])) {
      // This is OAuth 2.0 and we have a refresh token!
      $token = feeds_oauth_refresh2($token['oauth_refresh_token'], $uid, $id);
    }
    else {
      // Just clear it out -- the user will have to authenticate again.
      $token = NULL;
    }
  }

  return $token;
}
