<?php
/*
Plugin Name: Facebook Login
Description: Extends Website Membership to support login and sign-up via Facebook
Version: 1.04
Requires at least: 2.51
Required System Plugin: true
*/

// UPDATE THESE VALUES
$GLOBALS['FACEBOOKAPI_APP_ID']       = '1567266503589168'; // Copy facebook App ID from app created at https://developers.facebook.com/apps
$GLOBALS['FACEBOOKAPI_APP_SECRET']   = '5bc98880e08d3342f4055faceee9c4f3'; // Copy facebook App Secret from app created at https://developers.facebook.com/apps

// DON'T UPDATE ANYTHING BELOW THIS LINE
$GLOBALS['FACEBOOKAPI_ENABLE_LOGIN'] = true; // Set to true to enable facebook login
$GLOBALS['FACEBOOK_LOGIN_PLUGIN']    = true;

// plugin actions
addAction('section_init',                         '_facebookLogin_pluginMenuErrors', null, 1);
addAction('_websiteLogin_init.pre_actionHandler', '_facebookLogin_init', null, 0); // called by plugin hook in _websiteLogin_init()

// get configuration errors
function facebookLogin_getErrors($dieOnErrors = false) {

  // error checking
  $errors = array();
  if     (!@$GLOBALS['WEBSITE_MEMBERSHIP_PLUGIN'])               { $errors[] = t("Website Membership plugin must be enabled!"); }
  elseif (@$GLOBALS['WEBSITE_MEMBERSHIP_VERSION'] < 1.09)        { $errors[] = t("Website Membership plugin must be upgraded to 1.09 or newer!"); }
  if (!@$GLOBALS['FACEBOOKAPI_ENABLE_LOGIN'])                    { $errors[] = t("Facebook Login is currently disabled!"); }
  if (!@$GLOBALS['FACEBOOKAPI_APP_ID'])                          { $errors[] = t("You must enter a value for FACEBOOKAPI_APP_ID!"); }
  if (!@$GLOBALS['FACEBOOKAPI_APP_SECRET'])                      { $errors[] = t("You must enter a value for FACEBOOKAPI_APP_SECRET!"); }
  if ($errors) {
    $message = t("Facebook Login: ") . implode("<br/>\n", $errors) . "<br/>\n";
    if ($dieOnErrors) { die($message); }
    else              { return $message; }
  }

  return false;
}

//
function _facebookLogin_pluginMenuErrors($tableName = '') {
  $isPluginMenu = $tableName == 'admin' && @$_REQUEST['action'] == 'plugins';
  if ($tableName && !$isPluginMenu) { return; } // only run for viewers

  alert( facebookLogin_getErrors() );
}


// called by plugin hook in _websiteLogin_init()
function _facebookLogin_init() {

  // update schemas if needed
  _facebookLogin_updateAccountsSchema();

  // perform website login actions
  if (@$_REQUEST['action'] == 'fbLogin') {

    // popups: redirect parent page to new url and close popup
    if (@$_REQUEST['isPopup']) {
      print "<script>\n";
      // fix facebook bug where #_=_ gets appended to urls: https://developers.facebook.com/bugs/318390728250352
      // ref: https://www.google.com/search?q=facebook+login+%23_%3D_
      print "var url = window.location.href.split('#')[0]; // remove facebook added #_=_\n";

      print "url     = url.replace('&isPopup=1','&isPopup=0');\n";
      print "window.opener.location = url;\n";
      print "window.close();\n";
      print "</script>\n";
      exit;
    }

    _facebookLogin_loginFromFacebook();
  }

}

// make sure that all required facebook information is set
function _facebookLogin_updateAccountsSchema() {
  if (!$GLOBALS['FACEBOOKAPI_ENABLE_LOGIN']) { return; }

  // ensure membership account schema has appropriate fields
  $fieldsToEnsure = array(
    'facebook_id' => array(
      'order'            =>  time() + 10,
      'label'            =>  'Facebook ID',
      'type'             =>  'textfield',
      'isSystemField'    =>  1,
      'isUnique'         =>  1,
    ),
    'facebook_access_token' => array(
      'order'            =>  time() + 20,
      'label'            =>  'Facebook Access Token',
      'type'             =>  'textfield',
      'isSystemField'    =>  1,
      'isUnique'         =>  1,
    ),
  );

  // add fields to accounts table if necessary
  $accountSchema = loadSchema(accountsTable());
  $updated = false;
  foreach ($fieldsToEnsure as $newFieldName => $newFieldSchema) {
    if (!@$accountSchema[$newFieldName]) {
      $accountSchema[$newFieldName] = $newFieldSchema;
      $updated = true;
    }
  }

  // save schema if necessary
  if ($updated) {
    saveSchema(accountsTable(), $accountSchema );
    createMissingSchemaTablesAndFields();
  }

}

// get url to perform facebook login
function facebookLogin_getFacebookLoginUrl($popup=false) { // v2.51 added $popup
  facebookLogin_getErrors(true); // die on configuration errors

  // allow plugins to request additional permissions as necessary
  $scope = applyFilters("facebook_scope", "email");

  // get redirect url
  $redirect_uri = $GLOBALS['WEBSITE_LOGIN_LOGIN_FORM_URL']. "?action=fbLogin" . ($popup ? "&isPopup=1" : "");
  $redirect_uri = urlencode(realUrl($redirect_uri));

  // Reference: https://developers.facebook.com/docs/reference/dialogs/oauth/
  $url = "https://www.facebook.com/dialog/oauth?client_id=" . $GLOBALS['FACEBOOKAPI_APP_ID'];
  $url .= "&redirect_uri=$redirect_uri";
  $url .= "&scope=$scope";
  if ($popup) { $url .= "&display=popup"; }
  else        { $url = str_replace('&','&amp;', $url); }
  return $url;
}

//
function facebookLogin_getFacebookLoginUrl_popupOnClick() {
$url = escapeJs(facebookLogin_getFacebookLoginUrl(true));
$js = <<<__JS__
w=703;
h=334;
var left=(screen.width/2)-(w/2);
var top=(screen.height/2)-(h/2);
window.open('$url', 'fb_popup', 'toolbar=no,location=yes,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,,width='+w+',height='+h+',top='+top+',left='+left);
return false;
__JS__;
$js = preg_replace("/[\r\n]/", "", $js);
$js = htmlencode($js);
return $js;

}

// perform a login from facebook
// overview: https://developers.facebook.com/docs/howtos/login/server-side-login/
function _facebookLogin_loginFromFacebook() {
  facebookLogin_getErrors(true); // die on configuration errors

  global $CURRENT_USER;
  if (!@$_REQUEST['code']) { return; }

  ### get access token
  // Reference: https://developers.facebook.com/docs/howtos/login/server-side-login/
  // Example response (success): access_token=ABAEO78ypEZCgBANeNhMkYExZCtBfBMLRMnTQZAFTPgboSmICvzOUC3QW43nCapcHGN6ZBXJZC7Km73OUw68PDvjc8njfUje1EmH1rZBkbzXgZDZD&expires=5184000
  /* Example response (error):
    {
       "error": {
          "message": "Code was invalid or expired. Session does not match current stored session. This may be because the user changed the password since the time the session was created or Facebook has changed the session for security reasons.",
          "type": "OAuthException",
          "code": 100
       }
    }
  */
  $redirect_uri = $GLOBALS['WEBSITE_LOGIN_LOGIN_FORM_URL']. "?action=fbLogin" . (array_key_exists('isPopup', $_REQUEST) ? "&isPopup=1" : ''); // send identical redirect_url as popup did
  $redirect_uri = urlencode(realUrl($redirect_uri));

  $url  = "https://graph.facebook.com/oauth/access_token?client_id=" . $GLOBALS['FACEBOOKAPI_APP_ID'];
  $url .= "&client_secret=" . $GLOBALS['FACEBOOKAPI_APP_SECRET'];
  $url .= "&redirect_uri=" . $redirect_uri;
  $url .= "&code=" . $_REQUEST['code'];
  list($response) = getPage( $url );  // NOTE: Facebook returns JSON on errors and QueryString encoded values on success
  $json = json_decode($response, true);
  if (@$json['error']) {
    $error = sprintf("Facebook Error (%s): %s", @$json['error']['code'], @$json['error']['message']);
    alert($error);
    return false;
  }

  ### load facebook user
  $responseValues = array();
  parse_str( $response, $responseValues );
  if (!array_key_exists('access_token', $responseValues)) { die(__FUNCTION__ . ": No access_token!"); }

  // retrieve facebook user data based on the user's access token
  // Be sure to check for 'error' in your calling code.  Example error output from: https://graph.facebook.com/me?access_token=x
  // Reference: https://developers.facebook.com/docs/reference/api/user/
  $graph_url = "https://graph.facebook.com/v3.2/me?fields=email,name,first_name,last_name&access_token=" . $responseValues['access_token'];
  list($response) = getPage( $graph_url );
  $user           = json_decode( @$response, true );    // NOTE: remember to check for "error" in return value

  //
  if (@$user['error']) {
    $error = sprintf("Facebook Error (%s): %s", @$user['error']['code'], @$user['error']['message']);
    alert($error);
    return;
  }
  if (!@$user['email']) { die("Error: No facebook email found!"); } // this code show never run but prevents errors if an email isn't specified

  ### load website/cms user matching facebook_id or email
  $where     = mysql_escapef("facebook_id = ? OR email = ?", $user['id'], $user['email']);
  $localUser = mysql_get(accountsTable(), null, $where);
  if ($localUser) {
    mysql_update(accountsTable(), $localUser['num'], null, array( 'facebook_id' => $user['id'], 'facebook_access_token' => $responseValues['access_token'] ) );
  }
  else {
    $localUser = _facebookLogin_createUserFromFacebook( $user, $responseValues['access_token'] );
  }

  // emulate login
  websiteLogin_setLoginTo($localUser['username'], $localUser['password']);
  $GLOBALS['CURRENT_USER'] = _websiteLogin_getCurrentUser();
  doAction('wsm_loginSuccess');

  // redirect on success
  $postLoginUrl = coalesce( getPrefixedCookie('lastUrl'), @$GLOBALS['WEBSITE_LOGIN_POST_LOGIN_URL'], '/' );

  redirectBrowserToURL($postLoginUrl);
  exit;

}

// create an return a local user account based on facebook information
function _facebookLogin_createUserFromFacebook($user, $access_token) {
  $schema       = getSchemaFields(accountsTable());
  $passwordText = wsm_generatePassword();
  $passwordHash = getPasswordDigest( $passwordText );

  $columnsToValues = array(
    'createdDate='           =>  'NOW()',
    'updatedDate='           =>  'NOW()',
    'createdByUserNum'       =>  0,
    'updatedByUserNum'       =>  0,
    'password'               =>  $passwordHash,
    'facebook_id'            =>  $user['id'],
    'facebook_access_token'  =>  $access_token,
  );

  // adding available information to the new user record
  // a list of available fields can be found here http://developers.facebook.com/docs/reference/login/basic-info/
  $columnsToValues['email']    = $user['email'];
  $columnsToValues['username'] = $user['email'];
  if (array_key_exists( 'fullname', $schema )) {
    $columnsToValues['fullname'] = $user['name'];
  }

  $userNum   = mysql_insert(accountsTable(), $columnsToValues, true );
  $localUser = mysql_get(accountsTable(), $userNum );

  // send signup email
  list($mailErrors, $fromEmail) = wsm_sendSignupEmail($localUser['num'], $passwordText);
  if ($mailErrors) { die("Mail Error: $mailErrors"); }

  return $localUser;
}

//

// get an array of a users friends. Example usage below:
// $facebooksIdsToNames = facebookUtil_getFacebookFriends($facebook_id, $facebook_access_token, $friendLookupErrors);
// if ($friendLookupErrors) { die("Error looking up friends: $friendLookupErrors"); }
//
// Note: Be sure to check $errorMessage to catch errors, know that access_tokens can expire so you may need to
// load the friendlist when the user logs in or shortly after and save it, and this function downloads friends in
// batches of 5000 so for a user with millions of friends you'll want to consider timeout issues.
function facebookUtil_getFacebookFriends($facebook_id, $facebook_access_token, &$errorMessage = '') {
  $facebookIdsToNames = array();

  $appSecretProof= hash_hmac('sha256', $facebook_access_token, $GLOBALS['FACEBOOKAPI_APP_SECRET']); 

  // Reference: https://developers.facebook.com/docs/reference/api/user/#friends
  $requestUrl = "https://graph.facebook.com/v3.2/".$facebook_id."/friends?access_token=$facebook_access_token&appsecret_proof=$appSecretProof";
  while (1) {
    // get data
    list($responseRaw) = getPage($requestUrl);
    $response = json_decode($responseRaw, true);
    // return errors
    if (@$response['error']) {
      $errorMessage = @$response['error']['message'] . " (" .@$response['error']['type']. " #" . @$response['error']['code']. ")";
      break;
    }

    // parse data
    if (@$response['data']) {
      foreach (array_values($response['data']) as $friend) {
        $facebookIdsToNames[ $friend['id'] ] = $friend['name'];
      }
    }

    // get next page
    if (!@$response['paging']['next']) { break; }
    $requestUrl = @$response['paging']['next'];
  }

  //
  return $facebookIdsToNames;
}


?>