Posts tagged ‘CakePHP’

Set far-future expires headers with Rackspace Cloud Files (sort of)

To keep parity with existing relationships, a client has required that we work with Cloud Files instead of Amazon’s CloudFront. There seems to be quite a bit of confusion about which offering actually performs better, but from my perspective, the Amazon API is much more intuitive, and therefore easier to work with. That said, there seems to be a lot of misinformation out there about Cloud Files; after spending a day with the API, I am pleased to conclude that the API is not as bad as I had initially thought. Rackspace could likely benefit from a little platform evangelism…

The biggest issue I’ve seen reported from a number of sources is the 72 hour TTL limit via the Rackspace control panel. Cloud Files uses TTL as an expires header of sorts. So I jumped on support chat, and quickly learned that the 72 hour TTL is only a limitation of the control panel, and a far-future TTL can easily be set via the API.

For this particular project, I’m using CakePHP, so don’t get too caught up on that App::import static method. Assuming cloudfiles.php, cloudfiles_exceptions.php, and cloudfiles_http.php are all in a cloudfiles folder in the vendors directory…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function publish(){
    //load up the cloudfiles class
    App::import('Vendor', 'cloudfiles/cloudfiles');

    //create a connection with your Cloud Files username and API secret
    $auth = new CF_Authentication('USER_NAME', 'API_SECRET');
    $auth->authenticate();
    $conn = new CF_Connection($auth);

    //grab an instance of our container
    $container = $conn->get_container('CONTAINER_NAME');

    //get the container's current TTL (assuming this was an existing container)
    debug( $container->cdn_ttl );

    //now just call the make_public method with a TTL parameter
    //here I'm just setting it to 30 days
    $container->make_public(86400 * 30);

    //confirm the TTL was properly updated
    debug( $container->cdn_ttl );
    exit();
}

The script outputs TTL values of 259200 and 2592000 respectively; the TTL on the container is now 30 days instead of 3. I have not noticed the updated TTL on the Rackspace control panel yet, but I’ll update this post if it is accurate once the existing 72 hour TTL expires.

Save page output as HTML with CakePHP

I’m currently working on a project that requires storing dynamically generated HTML files on a CDN. Without getting too far off topic, I need to dynamically create an HTML page with database data and then store that HTML file on a CDN (Amazon S3) which cannot parse PHP. Even if it could parse PHP, these files could conceivably be hit very many times and this solution cannot be load dependent.

I was looking for a way to output a view, and save it as an HTML file which could then be uploaded via the S3 API. In the CakePHP API, we see the Controller render method returns the output of the View model’s render method which returns a string of the HTML output to be loaded in the browser. But we don’t want to call the Controller’s render method as that will send the user’s browser to that page; instead we want to use the View’s render method so we get the string value that can be saved into an HTML file.

Assume we have a publish controller method which is called when the user wants to publish the HTML file. We also have a private method to generate the HTML and save it as a file in the local filesystem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
class VideosController extends AppController {

    protected function publish( $id ){
        $html_file = $this->generate( (int)$id );
        //do whatever needs to be done with the HTML file
        //for example, this is where I upload the file to S3
    }

    private function generate( $id ){
        //set whatever data is required to build the page;
        //be sure this is done before instantiating the View class so all
        //set variables are passed when you pass this controller object in the constructor
        $video = $this->Video->read( null, $id );
        $this->set('video', $video);

        //instantiate a new View class from the controller
        $view = new View($this);

        //call the View object's render method which will return the HTML.
        //Note, I'm setting the layout to HTML and telling the view to render the html.ctp file in /app/views/videos/ directory
        $viewdata = $view->render(null,'html','html');

        //set the file name to save the View's output
        $path = WWW_ROOT . 'files/html/' . $id . '.html';
        $file = new File($path, true);

        //write the content to the file
        $file->write( $viewdata );

        //return the path
        return $path;

    }
}

Haven’t thoroughly tested this yet, so let me know if you run into any problems. But so far, seems to be working well. Note, this isn’t really production ready as the generate method assumes success.

Setting layout in error pages based on Auth status with CakePHP

CakePHP error pages load within the default layout. This works most of the time, but for some applications, I have a different layout file for logged in users. For example, the navigation changes when a user is logged in. Normally including the proper elements based on the user’s login status would be sufficient, but for a recent project, the entire layout changes based on the user status. Therefore I needed to find a way to be sure the proper layout was loaded when 404 errors appeared.

First thing to do is create an app_error.php file in your /app directory. Your AppError class should extend the ErrorHandler class. Now extend the error404 method. You’ll have a reference to the controller via $this->controller so that you can access the Auth component. So just see if we have a valid logged in user, and if not, set the layout to ‘guest’, or whatever your layout happens to be named.

Be sure to call the parent method, passing in the $params variable to be sure the error is handled properly by the ErrorHandler’s error404 method.

1
2
3
4
5
6
7
8
9
10
11
<?php
class AppError extends ErrorHandler {

    function error404($params) {
            if( !$this->controller->Auth->User() ){
                $this->controller->layout = "guest";
            }
            parent::error404($params);
    }

}

Integrating Facebook Connect with CakePHP’s Auth Component

This post is out of date; place check out any of the numerous Cakephp 2+ versions of Facebook Websites implementations out there.

I wanted to be able to leverage all of the advantages of using Cake’s built in Auth component in my latest application; problem was that the application needed to allow for both normal user accounts and Facebook Connect generated user accounts. I struggled for a while to find the most seamless approach, and then it clicked — dynamically set Auth->fields.

First, drop the facebook client libraries in a app/vendors/facebook folder. Then, in your app_controller, import the classes, and set some properties:

1
2
3
4
5
6
7
8
9
10
<?php
App::import('Vendor', 'facebook/facebook');

class AppController extends Controller {
    var $components = array('Auth');
    var $uses = array('User');
    var $facebook;
    var $__fbApiKey = 'your_key';
    var $__fbSecret = 'your_secret';
}

Next we’ll overwrite the inherited __construct method to instantiate a Facebook Object:

1
2
3
4
5
6
7
8
9
  function __construct() {
    parent::__construct();
       
    // Prevent the 'Undefined index: facebook_config' notice from being thrown.
    $GLOBALS['facebook_config']['debug'] = NULL;
       
    // Create a Facebook client API object.
    $this->facebook = new Facebook($this->__fbApiKey, $this->__fbSecret);
  }

In our beforeFilter method, we define the default Auth properties, call a private __checkFBStatus method, and pass any user data to the view:

1
2
3
4
5
6
7
8
9
10
11
function beforeFilter() {
    // Authentication settings
    $this->Auth->fields = array('username' => 'email', 'password' => 'password');
    $this->Auth->logoutRedirect = '/';

    //check to see if user is signed in with facebook
    $this->__checkFBStatus();

    //send all user info to the view
    $this->set('user', $this->Auth->user());
}

Next we’ll define the private method __checkFBStatus that’s called in our beforeFilter method. First we check to make sure $this->Auth->User isn’t already set by a normal user account, and we check to see if there’s a logged in facebook user. The logged in facebook user is available after we use the JavaScript API to log facebook users into the site.

1
2
3
    private function __checkFBStatus(){
        //check to see if a user is not logged in, but a facebook user_id is set
        if(!$this->Auth->User() &amp;&amp; $this->facebook->get_loggedin_user()):

Next, see if this user has already logged in, and therefore already has an entry in our User table:

1
2
3
4
5
6
7
            //see if this facebook id is in the User database; if not, create the user using their fbid hashed as their password
            $user_record =
                $this->User->find('first', array(
                    'conditions' => array('fbid' => $this->facebook->get_loggedin_user()),
                    'fields' => array('User.fbid', 'User.fbpassword', 'User.password'),
                    'contain' => array()
                ));

If no record was found, we create a new record for this user. We are setting 3 variables, fbid, fbpassword, and password. The fbpassword field will hold a randomly generated 20 character string un-hashed so that we can access the value of the field directly from the database. We retrieve this value based on the fbid field, hash it, and that’s our password as the Auth Component expects:

1
2
3
4
5
6
7
8
9
            //create new user
            if(empty($user_record)):
                $user_record['fbid'] = $this->facebook->get_loggedin_user();
                $user_record['fbpassword'] = $this->__randomString();
                $user_record['password'] = $this->Auth->password($user_record['fbpassword']);
               
                $this->User->create();
                $this->User->save($user_record);
            endif;

We need to then update our Auth Component’s fields property to use fbid as the username.

1
2
3
4
5
6
7
8
            //change the Auth fields
            $this->Auth->fields = array('username' => 'fbid', 'password' => 'password');

            //log in the user with facebook credentials
            $this->Auth->login($user_record);
           
        endif;
    }

Here’s the complete app_controller.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php
App::import('Vendor', 'facebook/facebook');

class AppController extends Controller {
    var $components = array('Auth');
    var $uses = array('User');
  var $facebook;
  var $__fbApiKey = 'your_key';
  var $__fbSecret = 'your_secret';
   
  function __construct() {
    parent::__construct();
       
    // Prevent the 'Undefined index: facebook_config' notice from being thrown.
    $GLOBALS['facebook_config']['debug'] = NULL;
       
    // Create a Facebook client API object.
    $this->facebook = new Facebook($this->__fbApiKey, $this->__fbSecret);
  }

    function beforeFilter() {
    // Authentication settings
    $this->Auth->fields = array('username' => 'email', 'password' => 'password');
    $this->Auth->logoutRedirect = '/';

        //check to see if user is signed in with facebook
        $this->__checkFBStatus();

        //send all user info to the view
        $this->set('user', $this->Auth->user());
  }

    private function __checkFBStatus(){
        //check to see if a user is not logged in, but a facebook user_id is set
        if(!$this->Auth->User() &amp;&amp; $this->facebook->get_loggedin_user()):

            //see if this facebook id is in the User database; if not, create the user using their fbid hashed as their password
            $user_record =
                $this->User->find('first', array(
                    'conditions' => array('fbid' => $this->facebook->get_loggedin_user()),
                    'fields' => array('User.fbid', 'User.fbpassword', 'User.password'),
                    'contain' => array()
                ));

            //create new user
            if(empty($user_record)):
                $user_record['fbid'] = $this->facebook->get_loggedin_user();
                $user_record['fbpassword'] = $this->__randomString();
                $user_record['password'] = $this->Auth->password($user_record['fbpassword']);
               
                $this->User->create();
                $this->User->save($user_record);
            endif;

            //change the Auth fields
            $this->Auth->fields = array('username' => 'fbid', 'password' => 'password');

            //log in the user with facebook credentials
            $this->Auth->login($user_record);
           
        endif;
    }

    private function __randomString($minlength = 20, $maxlength = 20, $useupper = true, $usespecial = false, $usenumbers = true){
        $charset = "abcdefghijklmnopqrstuvwxyz";
        if ($useupper) $charset .= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        if ($usenumbers) $charset .= "0123456789";
        if ($usespecial) $charset .= "~@#$%^*()_+-={}|][";
        if ($minlength > $maxlength) $length = mt_rand ($maxlength, $minlength);
        else $length = mt_rand ($minlength, $maxlength);
        $key = '';
        for ($i=0; $i<$length; $i++){
            $key .= $charset[(mt_rand(0,(strlen($charset)-1)))];
        }
        return $key;
    }

}
?>

Next we need to make a few changes to our default.ctp layout; first add the facebook namespace:

1
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">

Add these 2 javascript snippets (be sure your xd_receiver.htm file is accessible in your webroot):

1
2
<script type="text/javascript" src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php"></script>
<script type="text/javascript">FB.init("your_api_key","/xd_receiver.htm");</script>

Last step, place your login and logout buttons in your default view. Few things to point out here; onlogin=”window.location.reload();” will cause the page to reload after a successful Facebook Connect login. This is how we are able to access $this->facebook->get_loggedin_user() in our app_controller to force the manual Auth login. If the user is a facebook user (determined with $user['User']['fbid'] > 0), add an onclick event that calls the FB.Connect.logout() function with a redirect URL as the parameter. This will log the User out of the Auth Component after it logs the user out of Facebook.

1
2
3
4
5
6
7
8
9
10
11
<?php
            if(!empty($user)):
                if($user['User']['fbid'] > 0):
                    echo $html->link('logout', '#', array('onclick' => 'FB.Connect.logout(function() { document.location = \'http://your_server/users/logout/\'; }); return false;'));
                else:
                    echo $html->link('logout', array('controller' => 'users', 'action' => 'logout'));
                endif;
            else:
                echo '<fb:login-button onlogin="window.location.reload();"></fb:login-button>';
            endif;
?>

One last thing to consider, for security reasons… In your login method, be sure only users with fbid = 0 can login via the normal auth fields (username / password). Just an extra precaution considering you have an unhashed password in your database for those facebook users.

Hope that helps someone else out. Let me know if you run into any problems, I slapped this post up quickly and hopefully didn’t make many mistakes. And per Matt’s spot-on recommendation (via pseudocoder.com), I’m working on rolling this into a plugin… Will post when it’s complete.