Posts tagged ‘PHP’

Placing “moov atom” at the beginning of an MPEG-4 video with FFMpeg

According to Atomic Parsley’s article on media meta data, MPEG-4 files consist of atoms (or boxes). One of these important atoms is called the moov atom, and this moov atom contains mdat data which is important in communicating seek points. So long story short, after encoding a number of MP4 files I noticed that the entire video had to download before playing via Safari. The issues was that ffmpeg was placing the moov atom at the end of the mp4 file, so Safari could not determine seek points or other important pieces of information to allow the video to play before the entire file had downloaded (including the moov atom).

Fortunately ffmpeg is now packaged with a solution; qt-quickstart. I followed these steps to get qt-quickstart installed and integrated into my encoding process for mp4 files, and now videos play and can be ‘seek-ed’ before they have fully downloaded. Note, this assumes a fairly recent build of ffmpeg.

First, find your ffmpeg directory (I couldn’t remember where I installed it, so I just did a quick search for an ffmpeg directory):

1
$ find / -name 'ffmpeg' -type d

Next, install qt-quickstart by browsing to the ffmpeg folder returned above and running the following commands:

1
2
3
$ cd /usr/local/src/ffmpeg/
$ sudo make tools/qt-faststart
$ sudo checkinstall --pkgname=qt-faststart --pkgversion="$(date +%Y%m%d%H%M)-svn" --backup=no --deldoc=yes --fstrans=no --default install -D -m755 tools/qt-faststart /usr/local/bin/qt-faststart

Now it’s as simple as calling qt-quickstart via PHP; one thing to note is that qt-quickstart will generate a new file, so you may want to delete your old file (in this case, $target) after you run the qt-quickstart command.

1
2
$moov_atom = 'sudo qt-faststart "' . $target . '" "' . $destination . '"';
exec($moov_atom);

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);
    }

}

Converting Rail’s RESTful Authentication to PHP

A few months back we were rebuilding an existing site; the mandate was to use PHP for the upcoming release, but the existing site used Ruby on Rail’s RESTful Authentication gem to encrypt user passwords against a unique salt. Each user had both an encrypted password and a salt value that was being updated every time the user successfully logged in. Pretty smart.

Our problem was that we had to re-create the hashing of the user’s password so existing users could log into the site using their old password. With the code below, we are able to hash the user’s entered password in the same way that the Rails app did, allowing authentication to function properly in the new application. Fortunately the existing application used the default hashing method (we didn’t have access to the old site’s source code), so it was just a matter of digging around GIT to understand how the hashing process worked.

1
2
3
4
5
6
7
8
9
10
11
<?php
// $salt variable as retrieved from user's row based on supplied email address
$salt = '36493cef361b8b180863fe3e2685473f676359df';

$password = $_POST['password'];

//NOTE: this assumes a default Restful Auth setup
$password = sha1('--' . $salt . '--' . $password . '--');

//supplied password should now match encrypted database value if entered correctly
?>

Detecting AJAX requests with PHP

In an effort to make client-side code as unobtrusive as possible, we typically take the approach of first building applications without JavaScript.  This practice makes sure all of our anchor tags have legitimate URLs in their href attribute.  We can then attached event handlers to the anchors that should trigger AJAX requests.  Then it’s simply a matter of reading the href attribute of the clicked item, and setting that as the source for the AJAX request.  Nothing new here…

1
2
3
4
5
$('a.trigger').click(function(){
  var src = $(this).attr('href');
  //AJAX request here
  return false;
});

But often we want to fork our controller logic based on whether or not a request was made via AJAX.  For example, no reason getting data we won’t be using in the AJAX response (like navigations, user data, etc).  Plus we want to load AJAX-specific views to return appropriate HTML snippets, or JSON objects.  So we just test to see if $_SERVER['HTTP_X_REQUESTED_WITH'] equals “xmlhttprequest” which seems to work consistently for all of the browsers on our checklist (note, this is sent when an AJAX request is made via jQuery – and most other JavaScript frameworks – but best not to assume).  We set this as a constant in our bootstrap file, allowing us to refer to it throughout our code.

1
define('IS_AJAX', isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');

One issue this brought up though was caching of AJAX requests, especially in Internet Explorer.  When updating our HTML with the server’s response, we often saw the entire page load into the target div.  IE was aggressively caching the AJAX request.  Simply setting JQuery to disable caching was the easy fix.

1
$.ajaxSetup({ cache: false });

Another simple approach would be to add a parameter to all of your AJAX requests. And even though the method outlined above is not tamper proof (not that anything really is), it seems a bit more reliable to me than the AJAX parameter approach, especially when you have many hands in the jar during the application development cycle. For more complex application development, the majority of PHP frameworks out there provide this via other means (CakePHP’s RequestHandler for example). But for our home-baked, lightweight Facebook Application frameworks, this works nicely.

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.