As I said before, I've had some trouble with PHP Facebook development.. This time, I couldn't get a user ID because the user came from a non-Facebook page.

An application's drawing page is called it's "canvas page". It's the starting point for the whole app. Unfortunately, when the user jumps there from a bookmark, or enters the URL into the browser directly, Facebook can't supply the user ID. The app can't do anything, because it doesn't know who the heck it's working for. The user must visit your app's canvas page from a Facebook page.

Since your canvas page is a Facebook page, and the user just visited it directly, you'd think that redirecting to the same page would do the job. And it does! But only if the user clicks on a link.

Then there's the additional problem of users who don't allow you to see their user ID. You have to ask them for permission first.

I put together a skeleton that takes care of all these issues for FBML pages (I don't think it'll work for iFrame pages). Read on for the code.

First, we need to include some variables. For instance, you'll need to know your Facebook app id, API key, and secret. (I call these $fbappid, $fbapikey, and $fbsecret, respectively.) I keep these in a separate file, which I name config.inc.php. It looks like this:

// Facebook stuff
$fbappid = '<the app="" id="">';
$fbcanvas = '<url of="" the="" app="" canvas="" page="">';
$fbapikey = '<the app="" api="" key="">';
$fbsecret = '<the app="" secret="">';

You'll fill out your app's data, of course, which you can find in it's Developer profile. Include the data in the main program with a simple require statement:

require_once('config.inc.php');

Now we start checking for weird cases. For instance, some joker might examine the code and figure out where you keep your server. And then he'll visit you directly. Hence, this bit of code:

// Anyone who visits my server directly gets redirected to my FB profile page
if (!isset($_SERVER['HTTP_X_FB_USER_REMOTE_ADDR'])) {
    $app_profile = "http://www.facebook.com/apps/application.php?id=$fbappid";
    header('Location: ' . $app_profile);
    http_redirect($app_profile);
    exit;
}

If the user has created a bookmark for your app, he might visit while he's not logged in to Facebook. That'll never do! We'd better check for that, which Facebook indicates by setting 'fb_sig_logged_out_facebook' in the GET or POST variables:

// Logged-out users get a nice error message
if (isset($_REQUEST['fb_sig_logged_out_facebook'])) {
    echo "<error message="You must log in!">Please log in above to use <application-name></application-name></error>";
    exit;
}

Having covered the instant-failure cases, we can move on to the complicated cases. For these, we'll have to create a Facebook object and try to retrieve the user's ID. The user might be just visiting the canvas page, in which case we'd like to give him a preview of the app, with all the functionality we can provide. Alternatively, he might be a "logged in" user: a user who has given us additional permissions, and who can therefore access all our app's features. You call get_canvas_user() to get the ID for the casual user, and get_logged_in_user() for the full user:

require_once 'facebook.php';
$facebook = new Facebook($fbapikey, $fbsecret);

// Find Facebook user ID
//
// Is this a casual user? (Hasn't authorized me)
$user_id = $facebook-&gt;get_canvas_user();
if (empty($user_id)) {
    // Is this an authorized user?
    $user_id = $facebook-&gt;get_loggedin_user();
}

Unfortunately, this doesn't cover quite all the possibilities. If the user came from a non-Facebook page, the user ID won't be set at all, and we'll need him to click a link to get within the Facebook structure. If the user's privacy settings are restrictively high, the ID won't be set either, and he'll need to allow us to access his data. But how can we tell the difference? Easy: if he came from a Facebook page, '_fb_fromhash' is set.

if (empty($user_id)) {
    // We're on the canvas page, it should be Facebook framed.  
    // Make certain.  Could redirect us.
    $facebook-&gt;require_frame();  
    // If the 'came from Facebook' flag isn't set...
    if (!isset($_GET['_fb_fromhash'])) {
        // ...redirect to ourselves, with the flag set.
        // Rather than recreate the application blurb with a "Go to <appname>"
        // button, let's just redirect to the app profile page.  It's already
        // got the blurb, more information, AND a "Go to application" button.
        $app_profile = "http://www.facebook.com/apps/application.php?id=$fbappid";
        echo "<redirect url="$app_profile"></redirect>";
        exit;
    } else {
        // ... we already redirected once.  We must be in case 2.
        echo "<error message="Unable to identify user">Your privacy settings do not allow me to determine who you are.</error>\n";
        echo "<p>I can't do anything until I can recognize you.  Please <a href="%24fbcanvas" requirelogin="1">authorize <application-name linked="false"></application-name> to access your information</a> or change your profile to share more public information.</p>";
        exit;
    }
}

Once we've gotten pas this point, we're guaranteed to have a valid user ID in the $user_id variable. We can carry on with the actual application. We might want to save the user ID for later.

Put it all together, and you get a nice skeleton for an FBML Facebook app. I'm using this skeleton with my first Facebook app, which I hope to release soon.