diff --git a/docs/changelog/8.x.x.txt b/docs/changelog/8.x.x.txt
index 25798f40d..1016de841 100644
--- a/docs/changelog/8.x.x.txt
+++ b/docs/changelog/8.x.x.txt
@@ -1,4 +1,5 @@
8.0.0
- Replaced the existing caching mechanism with memcached, which results in a 400% improvement to cache speed. See migration.txt for API changes and gotcha.txt for prereq changes.
- Added "hot sessions" so sessions interact with the database less.
+ - Added Facebook Auth and FacebookLogin macro.
diff --git a/docs/gotcha.txt b/docs/gotcha.txt
index 8cffc0eee..ae71780d9 100644
--- a/docs/gotcha.txt
+++ b/docs/gotcha.txt
@@ -16,6 +16,7 @@ save you many hours of grief.
* WebGUI now requires the following modules
- Moose
- CHI
+ - Facebook::Graph
7.9.8
--------------------------------------------------------------------
diff --git a/etc/WebGUI.conf.original b/etc/WebGUI.conf.original
index 6a4ee6f4a..03e50b33f 100644
--- a/etc/WebGUI.conf.original
+++ b/etc/WebGUI.conf.original
@@ -190,7 +190,7 @@
# List the authentication plug-ins you wish to be available on
# this site.
-"authMethods" : [ "LDAP", "WebGUI" ],
+"authMethods" : [ "LDAP", "WebGUI", "Twitter", "Facebook"],
# List the merchant gateways you have installed and wish to be
# available on this site.
@@ -821,6 +821,7 @@
"EditableToggle" : "EditableToggle",
"e" : "e_companyEmail",
"Extras" : "Extras",
+ "FacebookLogin" : "FacebookLogin",
"FetchMimeType" : "FetchMimeType",
"FilePump" : "FilePump",
"FileUrl" : "FileUrl",
diff --git a/lib/WebGUI/Auth/Facebook.pm b/lib/WebGUI/Auth/Facebook.pm
new file mode 100644
index 000000000..3801e0950
--- /dev/null
+++ b/lib/WebGUI/Auth/Facebook.pm
@@ -0,0 +1,295 @@
+package WebGUI::Auth::Facebook;
+
+=head1 LEGAL
+
+ -------------------------------------------------------------------
+ WebGUI is Copyright 2001-2010 Plain Black Corporation.
+ -------------------------------------------------------------------
+ Please read the legal notices (docs/legal.txt) and the license
+ (docs/license.txt) that came with this distribution before using
+ this software.
+ -------------------------------------------------------------------
+ http://www.plainblack.com info@plainblack.com
+ -------------------------------------------------------------------
+
+=cut
+
+use strict;
+use base 'WebGUI::Auth';
+use Facebook::Graph;
+
+=head1 NAME
+
+WebGUI::Auth::Facebook -- Facebook auth for WebGUI
+
+=head1 DESCRIPTION
+
+Allow Facebook users to authenticate to WebGUI
+
+=head1 METHODS
+
+These methods are available from this class:
+
+=cut
+
+#----------------------------------------------------------------------------
+
+=head2 new ( ... )
+
+Create a new object
+
+=cut
+
+sub new {
+ my $self = shift->SUPER::new(@_);
+ return bless $self, __PACKAGE__; # Auth requires rebless
+}
+
+#----------------------------------------------------------------------------
+
+=head2 createFacebookUser ( fbuser )
+
+ my $user = $self->createFacebookUser( $fb->fetch('me') );
+
+Create a new Facebook::Graph user.
+
+=cut
+
+sub createFacebookUser {
+ my ( $self, $fbuser ) = @_;
+ my $user = WebGUI::User->create( $self->session );
+ $user->username( $fbuser->{name} );
+ $user->profileField('email', $fbuser->{email});
+ $user->profileField('firstName', $fbuser->{first_name});
+ $user->profileField('lastName', $fbuser->{last_name});
+ $self->saveParams( $user->userId, $self->authMethod, {
+ "facebookUserId" => $fbuser->{id},
+ } );
+ return $user;
+}
+
+#----------------------------------------------------------------------------
+
+=head2 editUserSettingsForm ( )
+
+Return the form to edit the settings of this Auth module
+
+=cut
+
+sub editUserSettingsForm {
+ my $self = shift;
+ my $session = $self->session;
+ my ( $setting ) = $session->quick(qw( setting ));
+ my $i18n = WebGUI::International->new( $session, 'Auth_Facebook' );
+
+ my $f = WebGUI::HTMLForm->new( $session );
+
+ $f->yesNo(
+ name => 'facebookAuthEnabled',
+ value => $setting->get( 'facebookAuthEnabled' ),
+ label => $i18n->get('enabled'),
+ hoverHelp => $i18n->get('enabled help'),
+ );
+
+ $f->text(
+ name => 'facebookAuthAppId',
+ value => $setting->get( 'facebookAuthAppId' ),
+ label => $i18n->get('app id'),
+ hoverHelp => $i18n->get('app id help'),
+ subtext => $i18n->get('get app id'),
+ );
+
+ $f->text(
+ name => 'facebookAuthSecret',
+ value => $setting->get( 'facebookAuthSecret' ),
+ label => $i18n->get('secret'),
+ hoverHelp => $i18n->get('secret help'),
+ );
+
+ $f->template(
+ name => 'facebookAuthTemplateIdChooseUsername',
+ value => $setting->get( 'facebookAuthTemplateIdChooseUsername' ),
+ label => $i18n->get('choose username template'),
+ hoverHelp => $i18n->get('choose username template help'),
+ namespace => 'Auth/Facebook/ChooseUsername',
+ );
+
+ return $f->printRowsOnly;
+}
+
+#----------------------------------------------------------------------------
+
+=head2 editUserSettingsFormSave ( )
+
+Process the form for this Auth module's settings
+
+=cut
+
+sub editUserSettingsFormSave {
+ my $self = shift;
+ my $session = $self->session;
+ my ( $form, $setting ) = $session->quick(qw( form setting ));
+
+ my @fields = qw(
+ facebookAuthEnabled facebookAuthAppId facebookAuthSecret
+ facebookAuthTemplateIdChooseUsername
+ );
+ for my $field ( @fields ) {
+ $setting->set( $field, $form->get( $field ) );
+ }
+
+ return;
+}
+
+#----------------------------------------------------------------------------
+
+=head2 getTemplateChooseUsername ( )
+
+Get the template to choose a username
+
+=cut
+
+sub getTemplateChooseUsername {
+ my ( $self ) = @_;
+ my $templateId = $self->session->setting->get('facebookAuthTemplateIdChooseUsername');
+ return WebGUI::Asset->newById( $self->session, $templateId );
+}
+
+#----------------------------------------------------------------------------
+
+=head2 getFacebook ( )
+
+Get the Facebook::Graph object with the appropriate keys
+
+=cut
+
+sub getFacebook {
+ my ( $self ) = @_;
+ my ( $url, $scratch, $setting ) = $self->session->quick( qw( url scratch setting ) );
+ if ( !$self->{_fb} ) {
+ my $fb = Facebook::Graph->new(
+ app_id => $setting->get( 'facebookAuthAppId' ),
+ secret => $setting->get( 'facebookAuthSecret' ),
+ postback => $url->getSiteURL . $url->page('op=auth&authType=Facebook&method=callback'),
+ );
+ if ($scratch->get('facebookAuthAccessToken')) {
+ $fb->access_token($scratch->get('facebookAuthAccessToken'));
+ }
+ $self->{_fb} = $fb;
+ }
+ return $self->{_fb};
+}
+
+#----------------------------------------------------------------------------
+
+=head2 www_login ( )
+
+Begin the login procedure
+
+=cut
+
+sub www_login {
+ my ( $self ) = @_;
+ my $session = $self->session;
+ my ( $url, $scratch, $setting ) = $session->quick( qw( url scratch setting ) );
+
+ my $auth_url = $self->getFacebook
+ ->authorize
+ ->extend_permissions(qw(email))
+ ->uri_as_string;
+
+ $session->http->setRedirect($auth_url);
+ return "redirect";
+}
+
+#----------------------------------------------------------------------------
+
+=head2 www_callback ( )
+
+Callback from the Facebook authentication. Try to log the user in, creating a
+new user account if necessary.
+
+If the username is taken, allow the user to choose a new one.
+
+=cut
+
+sub www_callback {
+ my ( $self ) = @_;
+ my $session = $self->session;
+ my ( $form, $scratch, $db, $setting ) = $session->quick(qw( form scratch db setting ));
+
+ # handle facebook stuff
+ my $fb = $self->getFacebook;
+ $fb->request_access_token($form->get('code'));
+ $scratch->set('facebookAuthAccessToken', $fb->access_token);
+ my $fbuser = $fb->fetch('me');
+
+
+ ### Log the user in
+ # Find their FB user ID
+ my $userId = $db->quickScalar(
+ "SELECT userId FROM authentication WHERE authMethod = ? AND fieldName = ? AND fieldData = ?",
+ [ "Facebook", "facebookUserId", $fbuser->{id} ],
+ );
+
+ # Returning user
+ if ( $userId ) {
+ my $user = WebGUI::User->new( $session, $userId );
+ $self->user( $user );
+ return $self->login;
+ }
+ # Otherwise see if their screen name exists and create a user
+ elsif ( !WebGUI::User->newByUsername( $session, $fbuser->{name}) ) {
+ my $user = $self->createFacebookUser( $fbuser );
+ $self->user( $user );
+ return $self->login;
+ }
+
+ # Otherwise ask them for a new username to use
+ my $i18n = WebGUI::International->new( $session, 'Auth_Facebook' );
+ my $tmpl = $self->getTemplateChooseUsername;
+ my $var = {
+ message => sprintf( $i18n->get("username taken"), $fbuser->{name} ),
+ };
+
+ return $tmpl->process( $var );
+}
+
+#----------------------------------------------------------------------------
+
+=head2 www_setUsername ( )
+
+Set the username for a fb user. Only used as part of the initial fb
+registration.
+
+=cut
+
+sub www_setUsername {
+ my ( $self ) = @_;
+ my $session = $self->session;
+ my ( $form, $scratch, $db ) = $session->quick(qw( form scratch db ));
+ my $i18n = WebGUI::International->new( $session, 'Auth_Facebook' );
+
+ # Don't allow just anybody to set a username
+ my $fb = $self->getFacebook;
+ return if $fb->access_token eq '';
+
+ my $fbuser = $fb->fetch('me');
+ $fbuser->{name} = $form->get('newUsername');
+
+ if ( !WebGUI::User->newByUsername( $session, $fbuser->{name} ) ) {
+ my $user = $self->createFacebookUser( $fbuser );
+ $self->user( $user );
+ return $self->login;
+ }
+
+ # Username is again taken! Noooooo!
+ my $tmpl = $self->getTemplateChooseUsername;
+ my $var = {
+ message => sprintf( $i18n->get("username taken"), $fbuser->{name} ),
+ };
+
+ return $tmpl->process( $var );
+}
+
+1;
diff --git a/lib/WebGUI/Macro/FacebookLogin.pm b/lib/WebGUI/Macro/FacebookLogin.pm
new file mode 100644
index 000000000..214a427a1
--- /dev/null
+++ b/lib/WebGUI/Macro/FacebookLogin.pm
@@ -0,0 +1,37 @@
+package WebGUI::Macro::FacebookLogin;
+
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2010 Plain Black Corporation.
+#-------------------------------------------------------------------
+# Please read the legal notices (docs/legal.txt) and the license
+# (docs/license.txt) that came with this distribution before using
+# this software.
+#-------------------------------------------------------------------
+# http://www.plainblack.com info@plainblack.com
+#-------------------------------------------------------------------
+
+use strict;
+
+=head1 NAME
+
+Package WebGUI::Macro::FacebookLogin
+
+=head1 DESCRIPTION
+
+Works with the Facebook Auth plugin to allow users to log in using facebook.
+
+=cut
+
+
+#-------------------------------------------------------------------
+sub process {
+ my $session = shift;
+ my $url = $session->url;
+ return sprintf '
',
+ $url->page('op=auth;authType=Facebook;method=login'),
+ $url->extras('macro/FacebookLogin/login-button.png');
+}
+
+1;
+
+#vim:ft=perl
diff --git a/lib/WebGUI/i18n/English/Auth_Facebook.pm b/lib/WebGUI/i18n/English/Auth_Facebook.pm
new file mode 100644
index 000000000..70d2c0f21
--- /dev/null
+++ b/lib/WebGUI/i18n/English/Auth_Facebook.pm
@@ -0,0 +1,74 @@
+package WebGUI::i18n::English::Auth_Facebook;
+
+use strict;
+
+our $I18N = {
+ 'enabled' => {
+ message => q{Enabled},
+ lastUpdated => 0,
+ context => q{Label for auth setting field},
+ },
+
+ 'enabled help' => {
+ message => q{Enabled Facebook-based login},
+ lastUpdated => 0,
+ context => q{Hover help for auth setting field},
+ },
+
+ 'get app id' => {
+ message => q{Get a Facebook App Id from http://apps.facebook.com/developer},
+ lastUpdated => 0,
+ context => q{Link to get a Facebook App Id},
+ },
+
+ 'app id' => {
+ message => q{Application Id},
+ lastUpdated => 0,
+ context => q{Label for auth setting field},
+ },
+
+ 'app id help' => {
+ message => q{The Application ID from your Facebook application settings},
+ lastUpdated => 0,
+ context => q{Hover help for auth setting field},
+ },
+
+ 'secret' => {
+ message => q{Application Secret},
+ lastUpdated => 0,
+ context => q{Label for auth setting field},
+ },
+
+ 'secret help' => {
+ message => q{The Facebook Application Secret from your application settings},
+ lastUpdated => 0,
+ context => q{Hover help for auth setting field},
+ },
+
+ 'choose username title' => {
+ message => q{Choose a Username},
+ lastUpdated => 0,
+ context => q{Title for screen to choose a username},
+ },
+
+ 'username taken' => {
+ message => q{That username "%s" is taken. Please choose a new username.},
+ lastUpdated => 0,
+ context => q{An error message for the choose a username screen},
+ },
+
+ 'choose username template' => {
+ message => q{Choose Username Template},
+ lastUpdated => 0,
+ context => q{Label for auth setting field},
+ },
+
+ 'choose username template help' => {
+ message => q{The template to choose a username if the user's screen name already exists},
+ lastUpdated => 0,
+ context => q{Hover help for auth setting field},
+ },
+};
+
+1;
+#vim:ft=perl
diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl
index 3535c58ea..ac8c71892 100755
--- a/sbin/testEnvironment.pl
+++ b/sbin/testEnvironment.pl
@@ -157,6 +157,7 @@ checkModule('HTTP::Exception', );
checkModule('Net::Twitter', "3.13006" );
checkModule('Number::Format', );
checkModule('Email::Valid', );
+checkModule('Facebook::Graph', '0.0505' );
failAndExit("Required modules are missing, running no more checks.") if $missingModule;
diff --git a/share/upgrades/7.9.12-8.0.0/facebook_auth.sql b/share/upgrades/7.9.12-8.0.0/facebook_auth.sql
new file mode 100644
index 000000000..2aedfa326
--- /dev/null
+++ b/share/upgrades/7.9.12-8.0.0/facebook_auth.sql
@@ -0,0 +1,4 @@
+insert into settings values ('facebookAuthEnabled',0);
+insert into settings values ('facebookAuthAppId','');
+insert into settings values ('facebookAuthSecret','');
+insert into settings values ('facebookAuthTemplateIdChooseUsername','anlFXped9lqXPThZTdFX0A');
\ No newline at end of file
diff --git a/share/upgrades/7.9.12-8.0.0/root_import_default-facebook-choose-username.wgpkg b/share/upgrades/7.9.12-8.0.0/root_import_default-facebook-choose-username.wgpkg
new file mode 100644
index 000000000..3b9bb8ef2
Binary files /dev/null and b/share/upgrades/7.9.12-8.0.0/root_import_default-facebook-choose-username.wgpkg differ
diff --git a/www/extras/macro/FacebookLogin/login-button.png b/www/extras/macro/FacebookLogin/login-button.png
new file mode 100644
index 000000000..4e7766bca
Binary files /dev/null and b/www/extras/macro/FacebookLogin/login-button.png differ