Cappy Popp’s Slides from GSP East: ‘Migrating from Facebook to Bebo’
Here are my slides from my presentation this morning at GSP East. Please let me know if you have any questions.
You can also get them on slideshare here.
Here are my slides from my presentation this morning at GSP East. Please let me know if you have any questions.
You can also get them on slideshare here.
Here is my slide deck from GSP East workshop on creating a Bebo Application.
Link to Slideshare.com. The notes for each slide contain additional information useful in building your first application.
Post any questions below or on the Bebo Forums.
I have been answering a lot of email requests for how to get started with a Bebo application. Since applications can be written in any language that supports interaction with the Bebo REST API, developers have a lot of choices in how they build their application. The focus of this article will be on the "officially supported language" - PHP. I saw officially supported, because the Bebo Platform Team has released a simple PHP wrapper that makes using the REST API very simple, and they update this from time to time.
Before creating an application, you need to understand the parts of an application and how they interact with Bebo. I will go over each part below, and then show an example of a very simple application that uses each part.
The Bebo URL - this is the URL that users go to on Bebo. It is in the format "http://apps.bebo.com/yourapp/"
The Callback URL - this is the URL on your server that Bebo redirects to. It is where the actual application lives.
The Canvas Page - this is the main page of a Bebo application. It takes up the entire web page, except for the Bebo top header and footer. User's going to your canvas page are using your server directly via the callback URL. Canvas pages can either be written in SNML, a markup language that is standardized across Facebook and Bebo and is fast to load, or in an iFrame. Javascript is only allowed in iFrame based applications, but they cannot use SNML.
The Profile Box - each application can create a presence on the installing user's Profile page. This contents of this box are set by the application, but when the user is seeing this Profile box, it is running a cached version on Bebo's servers. The user can click links or interact with Flash to go to the canvas page or call back to your server using simple AJAX. Since browsing other people's Profiles is one of the main activities on Bebo, creating a compelling Profile Box is a very important part of helping your application to spread.
Invitations - each application can provide a way for users to recommend it to their friends. Bebo provides a common dialog for selecting friends, and limits the total number of invitations that can be send per person per day. You can control some of the text that the inviter sees when selecting their friends, and some of the text in the actual invitation. Your application must give the user a reason to want to send an invitation, either by being innately good, or by some type of reward system for invited friends.
News Stories - applications can produce news stories based on how the users interact with the application. These stories appear on the user's Profile page in the news section. Bebo will also show the most interesting stories from a user's friends on their home page. News stories should be interesting and actionable in order to help your application spread and provide value to the user.
require_once "bebo.php";
$appVisibleName = "Example App"; $appApiKey = 'copy API Key from the developer application page'; $appSecret = 'copy API Secret from the developer application page'; $appCallback = 'put in your callback url. Ex - http://myserver.com/favoritebirds/ '; $appBeboURL = 'put in the Application URL above - http://apps.bebo.com/favoritebirds/';
$bebo = new Bebo( $appApiKey, $appSecret ); $userID = $bebo->require_add();
displayPage($bebo); function displayPage($bebo) { $userID = $bebo->user; $output = "Welcome back, <sn:name uid='$userID' useyou='false' />.<br/> You have a nice picture:<sn:profile-pic uid='$userID' linked='false'/>"; echo $output; }
You now have a fully functional Bebo application. But, let's add some more features.
updateProfile($bebo); function updateProfile($bebo) { global $appVisibleName; $userID = $bebo->user; $snml = "This is the $appVisibleName profile box of <sn:name uid='$userID' useyou='false' /> <br/><sn:profile-pic uid='$userID' size='square' linked='false'/>"; $bebo->api_client->profile_setSNML($snml); }
publishStory($bebo); function publishStory($bebo) { global $appVisibleName; global $appBeboURL; $actor = $bebo->user; $title_template = "{actor} used <a href='$appBeboURL'>$appVisibleName</a>"; $title_data = null; $body_template = null; $body_data = null; $body_general = "Everyone should try $appVisibleName. <a href='$appBeboURL'> Install $appVisibleName today.</a>"; $image1 = null; $image1Link = null; try { $result = $bebo->api_client->feed_publishTemplatizedAction( $actor, $title_template, $title_data, $body_template, $body_data, $body_general, $image1, $image1Link, NULL, NULL, NULL, NULL, NULL, NULL, ""); } catch( Exception $ex) { } }
$invitePageURL = getInvitePageURL($bebo); echo "<a href='$invitePageURL'>Would you like to invite some friends?</a>"; function getInvitePageURL($bebo) { global $appApiKey; global $appBeboURL; global $appVisibleName; $beboInvitePage = "http://www.bebo.com/multi_friend_selector.php"; // What the inviter sees $actionText = urlencode("Which friends do you want to invite?"); $action = $appBeboURL; $type = urlencode("Invite"); // What the recipient sees $acceptInviteButtonURL = $bebo->get_add_url($appBeboURL); $acceptInviteButtonLabel = "Add $appVisibleName"; $acceptInviteButton = "<sn:req-choice url='$acceptInviteButtonURL' label='$acceptInviteButtonLabel' />"; $content = urlencode("Hey, try out $appVisibleName. $acceptInviteButton"); $sig = 0; // update with the signature tool. This is a two step process. Generate the url for the invite page // Then go to http://www.bebo.com/AppToolSig.jsp and paste it in. At the bottom, you will get a signature. Update the value above. // If you change the invite text, you will have to regenerate the sig above $invitePageURL = "$beboInvitePage?sig=$sig&api_key=$appApiKey&content=$content&type=$type &action=$action&actiontext=$actionText&invite=true"; return $invitePageURL; }
Here is the completed sample:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <?php date_default_timezone_set("America/New_York"); require_once "bebo.php"; // Global definitions for your app $appVisibleName = "Favorite Birds"; $appApiKey = "copy API Key from the developer application page"; $appSecret = "copy API Secret from the developer application page"; $appCallback = "http://myserver.com/favoritebirds "; $appBeboURL = "http://apps.bebo.com/favoritebirds/"; ///Main Entry Point /// // Force everyone to add the application. // If they have already added it, then display the page $bebo = new Bebo( $appApiKey, $appSecret ); $userID = $bebo->require_add(); displayPage($bebo); // Update the user's profile updateProfile($bebo); // Publish a news story publishStory($bebo); ///End Main Entry Point /// function displayPage($bebo) { $userID = $bebo->user; $output = "Welcome back, <sn:name uid='$userID' useyou='false' />.<br/> You have a nice picture:<sn:profile-pic uid='$userID' linked='false'/>"; // Add the link to the invite page $invitePageURL = getInvitePageURL($bebo); $output .= "<br/><a href='$invitePageURL'>Would you like to invite some friends?</a>"; echo $output; } // returns the url for the Bebo standard invite page function getInvitePageURL($bebo) { global $appApiKey; global $appBeboURL; global $appVisibleName; $beboInvitePage = "http://www.bebo.com/multi_friend_selector.php"; // What the inviter sees $actionText = urlencode("Which friends do you want to invite?"); $action = $appBeboURL; $type = urlencode("Invite"); // What the recipient sees $acceptInviteButtonURL = $bebo->get_add_url($appBeboURL); $acceptInviteButtonLabel = "Add $appVisibleName"; $acceptInviteButton = "<sn:req-choice url='$acceptInviteButtonURL' label='$acceptInviteButtonLabel' />"; $content = urlencode("Hey, try out $appVisibleName. $acceptInviteButton"); $sig = 0; // update with the signature tool. This is a two step process. Generate the url for the invite page // Then go to http://www.bebo.com/AppToolSig.jsp and paste it in. At the bottom, you will get a signature. Update the value above. // If you change the invite text, you will have to regenerate the sig above $invitePageURL = "$beboInvitePage?sig=$sig&api_key=$appApiKey&content=$content&type=$type&action=$action&actiontext=$actionText&invite=true"; return $invitePageURL; } function updateProfile($bebo) { global $appVisibleName; $userID = $bebo->user; $snml = "This is the $appVisibleName profile box of <sn:name uid='$userID' useyou='false' /> <br/><sn:profile-pic uid='$userID' size='square' linked='false'/>"; $bebo->api_client->profile_setSNML($snml); } function publishStory($bebo) { global $appVisibleName; global $appBeboURL; $actor = $bebo->user; $title_template = "{actor} used <a href='$appBeboURL'>$appVisibleName</a>"; $title_data = null; $body_template = null; $body_data = null; $body_general = "Everyone should try $appVisibleName. <a href='$appBeboURL'>Install $appVisibleName today.</a>"; $image1 = null; $image1Link = null; try { $result = $bebo->api_client->feed_publishTemplatizedAction( $actor, $title_template, $title_data, $body_template, $body_data, $body_general, $image1, $image1Link, NULL, NULL, NULL, NULL, NULL, NULL, ""); } catch( Exception $ex) { } } ?>
The example above just forces everyone to add, but isn't able to specially handle new installs or removals. You can handle those by looking at the $_REQUEST parameters that Bebo passes your application. These parameters show that users come to your application in 1 of 3 states:
By changing the block marked ///Main Entry Point /// above to one that detects the state, you can handle each one differently. You will need to add new functions to handle new user installs and uninstalls
///Main Entry Point /// // An existing user will have the fb_sig_in_canvas variable set, unless they are removing if (isSet($_POST) && isSet($_POST['fb_sig_in_canvas'])) { // Bebo will pass their user id and that they have added the application if ( isSet($_POST['fb_sig_user']) && $_POST['fb_sig_added'] == 1 ) { $userID = $_POST['fb_sig_user']; // If the user has just installed, Bebo will pass installed as a GET parameter if ( isSet($_GET) && isSet($_GET['installed']) ) { newInstall($userID); } // Normal users will go through here, so display the page $bebo = new Bebo( $appApiKey, $appSecret ); displayPage($bebo); // Update the user's profile updateProfile($bebo); // Publish a news story publishStory($bebo); } } // If you set up your own post add handler, then you must handle the add request the way you want, // and then redirect back to your Bebo URL. If you don't specify a post add handler, Bebo does this for you. else if ( isSet($_GET) && isSet($_GET['installed']) ) { $bebo = new Bebo( $appApiKey, $appSecret ); newInstall($bebo ); $bebo->redirect($appBeboURL); } // If you specify a post remove URL, then Bebo will call it with this POST variable. // After this call, you won't get anything else from this user unless they re-add your application else if ( isSet($_POST) && isSet($_POST['fb_sig_uninstall']) ) { $userID = $_POST['fb_sig_user']; uninstallUser( $userID); } // If the user goes to your callback URL directly, they didn't come in from Bebo, and you will have no information about the user // In most cases, you will just want to force the user to add the application by using require_add. This will force them back through // the existing user path above else { $bebo = new Bebo( $appApiKey, $appSecret ); $bebo->require_add(); } // Handle a new user installation function newInstall($userID) { // We can use the SNML to say hello to the user $output = "Hello, <sn:name uid='$userID' useyou='false' />. Thanks for adding the application!"; echo $output; } function uninstallUser($userID) { // You can't actually display anything to the user, or redirect them. // All you can do is clean up inside your application. } ///End Main Entry Point ///
Thought Labs provides custom software development and consulting services with a specialization in Social Network technologies. We build branded Facebook Pages for your company or multiple Pages for your various products. In addition, we create rich interactive applications on your Page to help users find your products and services. Thought Labs can also create custom Facebook, Bebo or OpenSocial applications carrying your message and your brand with them.
We pride ourselves on the quality of our work. We employ top application developers who are creative, highly skilled, up-to-date on the Social Networks' constantly changing APIs, and excellent at project management. We set high standards for ourselves and we meet them. In this new world of social marketing, Thought Labs delivers extremely innovative, high-quality work on time and on budget. Find out more at http://www.thoughtlabs.com.
I came across this issue today where I was getting an error when trying to add the <Fb:page-admin-edit-header/> to a canvas page of a page-only app.
The app was installed on a Facebook Page of which I was an admin.
Basically I had the following situation:
[0] canvas page that had <Fb:page-admin-edit-header/> rendered correctly at top when the canvas page was first hit
[1] canvas page had a simple non-Ajax form that submitted back to itself via POST
[2] POST url just contained URL of page
When I submitted the form, I kept getting the following error:
---------------------
Errors while loading page from application
Runtime errors:
fb:page-admin-edit-header can only display on a fbpage application.
There are still a few kinks Facebook and the makers of Accountability Journal-cp are trying to iron out. We appreciate your patience as we try to fix these issues. Your problem has been logged - if it persists, please come back in a few days. Thanks!
---------------------
I verified that the app was indeed a 'page-only' app, and that I had admin rights to the Facebook Page on which it was installed.
It seemed to me that this app was working perfectly until today, though I may be wrong about that...
It turns out that you MUST add the 'fb_page_id=[pageID]' URL param to the POST URL. Even though all of the FB variables were present in the original POST (fb_sig_page_id, fb_sig_is_admin=1, etc.) it appears that you must still provide the URL param in order for the FBML processing to insert the <Fb:page-admin-edit-header/> correctly.
Doorbell on Bebo is growing steadily, despite some randomness in the Bebo platform uptime. One side effect of its popularity was a new error – Too Many Connections. Here is a quick overview of the cause and a solution.
Too Many Connections can be caused by either a lot of simultaneous connections or by old connections not being released soon enough. There are some simple changes you can make to your PHP code and your MySQL settings to prevent both.
There are two built in ways to connect to MySQL from PHP – permanent or interactive. You get a permanent connection using mysql_pconnect(). This creates a permanent connection to the database (permanent is defined as 8 hours by the MySQL wait_timeout system variable). It will only create a new connection if it cannot find an existing permanent connection to reuse. You need to be careful with mysql_pconnect() to make sure that you don’t run out of connections, since these stay open so long and you cannot close them with mysql_close().
The other way is to use mysql_connect(). This creates a new temporary connection, if the new_link parameter is set to true, or reuses an existing one if it is not. This can be better than mysql_pconnect() because these connections can be short lived, and can be closed when needed with mysql_close(). An important parameter is the CLIENT_INTERACTIVE flag. If this is passed, it will use the MySQL interactive_timeout value instead of wait_timeout. Since wait_timeout defaults to 8 hours, this seems like a great idea. However, surprisingly, interactive_timeout also defaults to 8 hours. You should change this value to something smaller that fits your system.
Using mysql_connect() with new_link set to false, passing the CLIENT_INTERACTIVE flag, and adjusting interactive_timeout will help stop problems with old connections jamming up the works.
To deal with many simultaneous connections, adjust the MySQL max_connections variable. By default, it is set to 100. Increase this value to something that will support what you feel is realistic for your system.
A final thing to do after updating your code and MySQL is to flush the bad connections database with “myqladmin flush-hosts”. This will allow your web server to connect back to MySQL after all those Too Many Connection errors.
The Facebook API does have a way to set and get cookies, but it is in beta so is not currently part of the FB PHP libraries. I found it out by searching through forums, by trial-and-error, and on the FB Beta docs page. There are 2 functions available:
data.getCookies( user_id, cookie_name ); data.setCookie( user_id, cookie_name, cookie_value, expires, path );
To use these functions from PHP I simply wrapped them in functions that call the RESTful APIs correctly:
// to get cookies for a given user (optionally by name): function get_cookie($uid, $name=null) { global $facebook; return $facebook->api_client->call_method('data.getCookies', array('uid' => $uid, 'name' => $name) ); } // to set a cookie for a given user: function set_cookie($uid, $name, $value, $expires=null, $path=null){ global $facebook; return $facebook->api_client->call_method('data.setCookie', array('uid' => $uid, 'name' => $name, 'value' => $value, 'expires' => $expires, 'path' => $path) ); }
PHP 4.x introduced magic methods with the __serialize and __unserialize methods. Since then many have been added. Love them or hate them as a language abomination magic methods are here to stay. Most are well-documented but one, the __set_state() magic method, is not. I hope to change that a bit.
First, a simple description of magic methods is in order. Basically these are special callback functions that are called by PHP in specific circumstances; they usually allow you to do custom processing before or after a certain PHP library function call occurs. For example, when serializing objects, PHP will attempt to call the magic method/member function __sleep() prior to serialization. This is to allow the object to do any last minute clean-up, etc. prior to being serialized. Likewise, when the object is restored using unserialize() the __wakeup() magic method/member function is called. When trying to call a non-existent method on an object instance, PHP will attempt to call the magic method __call on that instance.
Before I go further, I want to address the problem of magic method performance in PHP. Yes, it has been well-documented that using these methods can be 10 – 20 times slower than using a normal method. However, under normal usage patterns no one should be able to detect such performance bottlenecks. Sure, if you use __call 10 million times, you are certainly going to see a hit. Chances are if your PHP code is under tight performance constraints, well, you most likely should not be using PHP to begin with.
So, let's get back to our buddy __set_state. What does it do? Like the __toString() magic method it is used as a formatting callback when your object instance is asked to be converted to a string (note that before PHP 5.2 only a call to echo() or print() resulted in a call to __toString.) The difference is in what event forces the callback. __set_state is called in response to an instance of your object being passed to the var_export function, while __toString is called otherwise. What's the difference? var_export is very different from the other debugging output functions (print_r/var_dump) in that it always must output PARSEABLE PHP CODE. The issue here is that var_export takes an optional second parameter – a boolean that determines whether after the variable passed as the first argument is returned as a string instead of output.
A quick example is in order. What do you think is output from the following code?
class Dummy { private $value1_; private $value2_; function __construct() { $this->value1_ = 100; $this->value2_ = "100"; } } $aDummy = new Dummy(); var_export($aDummy);
Here's the output (on screen):
Dummy::__set_state(array( 'value1_' => 100, 'value2_' => '100', ))
Now we're getting somewhere. var_export must print out valid PHP code, remember? And this is valid PHP code. It's referring to a static __set_state method in our Dummy class. Well, then you should be able to execute it. The PHP eval function evaluates the string argument passed to it as valid PHP code so we can use it to test this code. Let's replace that var_export call with this line:
eval(var_export($aDummy, true) . ';'));
What's the output? This is:
Fatal error: Call to undefined method Dummy::__set_state() in D:\src\phptests\__set_state.php(21) : eval()'d code on line 1
As you can see, var_export cannot produce executable PHP code for the Dummy type without a __set_state method defined. We need to provide one. var_export takes one argument, and it must be an array. It will contain key-value pairs of the properties or fields of the instance on which it is called.
class Dummy { private $value1_; private $value2_; function __construct() { $this->value1_ = 100; $this->value2_ = "100"; } static function __set_state(array $array) { foreach($array as $k => $v) { echo("$k ==> $v <br/>"); } } }
Now the output is this:
value1_ ==> 100 value2_ ==> 100
Better.
But what do you have to put in your __set_state method? That's completely up to you. It just should produce valid PHP code. It's also important to remember that if you're using some 3rd party logging, tracing, or debugging package that it might call var_export on your instance so it's best to be sure. Personally, I think the best thing to do is to basically treat __set_state as another creation method (a copy constructor for those of you with C++ backgrounds). That way if someone requests the evaluation mode in var_export they get expected behavior. Let's change __set_state in our example to reflect this idea:
static function __set_state(array $array) { $tmp = new Dummy(); $tmp->value1_ = $array['value1_']; $tmp->value2_ = $array['value2_']; return $tmp; }
Hopefully that has alleviated some of the confusion around this method. Enjoy!
For the longest time I have used 4NT as a replacement for the Windows CMD.exe shell. It provides an environment variable, _CWD, that provides the current working directory. I never knew that Windows also provides the same thing, in the form of the CD environment variable. This led me to look for other system-wide default dynamic variables that Windows provides.
I've found these:
| Variable | Sample Typical Value |
|---|---|
| %CD% | The current directory. |
| %DATE% | Current date in the format set by the Date command |
| %TIME% | Current time in the format set by the Time command |
| %ERRORLEVEL% | A number defining exit status of a previous command or called executable. |
| %RANDOM% | A random number between 0 and 32767. |
I use the console in Firebug all the time to test out one-liners in Javascript (like regexes for instance), but I had no idea that you could type multiple lines of JS in Firebug and run it. As I should have known of course they implemented this. Here's how.
You can either just paste a block of code into the console edit field (that's the line at the bottom of the console window that starts with '>>>' and has a flashing cursor), or click the little red icon at the lower right side of the console window.
Either way, it brings up the Firebug JavaScript editor window:

Just click the 'Run' button at the bottom, tweak as necessary, and copy into your final desitination!