<?php
/*
 * PiAnoS (Picture Annotation System), A fingerprint examiner training tool
 * Copyright (C) 2011-2012 University of Lausanne, Switzerland
 * https://ips-labs.unil.ch/pianos
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
 */

if( ! function_exists( 'http_response_code' ) )
{
    function http_response_code( $newcode = NULL )
    {
        static $code = 200;
        if( $newcode !== NULL )
        {
            header( 'X-PHP-Response-Code: ' . $newcode, true, $newcode );
            if( !headers_sent() )
                $code = $newcode;
        }
        return $code;
    }
}

function processImageUploads2( $type = NULL, $defaultDPI = NULL )
{
    /*
     *  TODO: fuse the two functions into one!
     */
    if( isset( $_FILES[ 'file' ] ) )
    {
        if( $_FILES[ 'file' ][ 'error' ] == 0 && $_FILES[ 'file' ][ 'size' ] > 0 && strlen( $_FILES[ 'file' ][ 'tmp_name' ] ) > 0 && strlen( $_FILES[ 'file' ][ 'name' ] ) > 0 )
        {
            try {
                $imageName = $_FILES[ 'file' ][ 'name' ];
                $filename = $_FILES[ 'file' ][ 'tmp_name' ];
                
                $message = '';
                
                // Open the file
                $handle = fopen( $filename, 'rb' );
                
                if( ! $handle )
                    throw new Exception( "The file could not be read." );
                
                // Check that the actual size match the received size (unnecessary ?)
                fseek( $handle, 0, SEEK_END );
                $size = ftell( $handle );
                fseek( $handle, 0, SEEK_SET );
                
                if( $size !== ( int ) $_FILES[ 'file' ][ 'size' ] )
                    throw new Exception( "File sizes mismatch." );
                
                // Set the informations of the image
                $width = NULL;
                $height = NULL;
                $resolution = NULL;
                $mimetype = NULL;
                $usedDefaultResolution = false;
                
                // First, try to read the EXIF meta-data
                $imagetype = exif_imagetype( $_FILES[ 'file' ][ 'tmp_name' ] );
                
                if( $imagetype )
                {
                    $metadata = NULL;
                    try
                    {
                        $metadata = @exif_read_data( $filename, 'FILE', true );
                        
                    } catch( Exception $e )
                    {
                        // First, if no default resolution has been set up, there's nothing we can do
                        if( ! $defaultDPI )
                            throw $e;
                            
                            $nonExifMetadata = array ();
                            $result = getimagesize( $filename, $nonExifMetadata );
                            
                            if( isset( $result[ 0 ] ) && ( int ) $result[ 0 ] > 0 && isset( $result[ 1 ] ) && ( int ) $result[ 1 ] > 0 && isset( $result[ 'mime' ] ) && strpos( $result[ 'mime' ], 'image' ) === 0 )
                            {
                                $width = ( int ) $result[ 0 ];
                                $height = ( int ) $result[ 1 ];
                                $mimetype = $result[ 'mime' ];
                                $resolution = ( int ) $defaultDPI;
                                $usedDefaultResolution = true;
                            }
                    }
                    
                    if( $metadata )
                    {
                        // Get the mimetype from the FILE section
                        if( array_key_exists( 'FILE', $metadata ) && array_key_exists( 'MimeType', $metadata[ 'FILE' ] ) )
                            $mimetype = $metadata[ 'FILE' ][ 'MimeType' ];
                            
                        // Find the width and height in the COMPUTED section
                        if( array_key_exists( 'COMPUTED', $metadata ) )
                        {
                            if( array_key_exists( 'Width', $metadata[ 'COMPUTED' ] ) )
                                $width = ( int ) $metadata[ 'COMPUTED' ][ 'Width' ];
                                if( $width !== NULL && $width <= 0 )
                                    $width = NULL;
                                    
                                    if( array_key_exists( 'Height', $metadata[ 'COMPUTED' ] ) )
                                        $height = ( int ) $metadata[ 'COMPUTED' ][ 'Height' ];
                                        if( $height !== NULL && $height <= 0 )
                                            $height = NULL;
                        }
                        
                        // Find the resolution in the XFD0 section
                        if( array_key_exists( 'IFD0', $metadata ) )
                        {
                            $xresolution = NULL;
                            if( array_key_exists( 'XResolution', $metadata[ 'IFD0' ] ) )
                            {
                                $xresolutionarr = explode( '/', $metadata[ 'IFD0' ][ 'XResolution' ] );
                                if( count( $xresolutionarr ) == 2 )
                                    $xresolution = ( int ) $xresolutionarr[ 0 ] / ( int ) $xresolutionarr[ 1 ];
                            }
                            if( $xresolution !== NULL && $xresolution <= 0 )
                                $xresolution = NULL;
                                
                                $yresolution = NULL;
                                if( array_key_exists( 'YResolution', $metadata[ 'IFD0' ] ) )
                                {
                                    $yresolutionarr = explode( '/', $metadata[ 'IFD0' ][ 'YResolution' ] );
                                    if( count( $yresolutionarr ) == 2 )
                                        $yresolution = ( int ) $yresolutionarr[ 0 ] / ( int ) $yresolutionarr[ 1 ];
                                }
                                if( $yresolution !== NULL && $yresolution <= 0 )
                                    $yresolution = NULL;
                                    
                                    if( $xresolution !== NULL && $yresolution !== NULL )
                                    {
                                        if( $xresolution !== $yresolution )
                                            throw new Exception( "X and Y resolutions mismatch (XRes is " . $xresolution . " dpi and YRes is " . $yresolution . " dpi)." );
                                            $resolution = $xresolution;
                                    }
                        }
                        
                        // If the resolution is still NULL at this point, it means that the IDF0 section of
                        // the EXIF data was either missing or did not include the resolution definition. At
                        // this point, and if the 'images_default_resolution' setting is defined, we apply it.
                        if( $resolution === NULL && $defaultDPI )
                        {
                            $resolution = ( int ) $defaultDPI;
                            $usedDefaultResolution = true;
                        }
                    }
                    
                } else {
                    throw new Exception( "The file is not an image, or its type is unknown/unsupported." );
                }
                
                if( ! $width || ! $height || ! $resolution || ! $mimetype )
                {
                    throw new Exception( "The required meta-data (MIME type, width, height and resolution) was not found." );
                }
                
                if( $type !== NULL )
                    $imageType = $type;
                else
                    $imageType = $_POST[ 'type' ];
                
                // Will hold actual bytes to store in the DB
                $buffer = NULL;
                
                // Perform on-the-fly conversions if needed
                $convData = Application::get( 'convert_images' );
                if( $convData )
                {
                    // throw new Exception(var_dump_str(array($mimetype,$convData)));
                    if( array_key_exists( $mimetype, $convData ) )
                    {
                        fclose( $handle );
                        $buffer = imagick_wrap( 'convert', array (
                            'filename' => $filename,
                            'extension' => $convData[ $mimetype ]
                        ) );
                        $imageName .= '.' . $convData[ $mimetype ];
                        $mimetype = 'image/' . $convData[ $mimetype ];
                    }
                }
                
                if( ! $buffer )
                {
                    $buffer = fread( $handle, $size );
                    fclose( $handle );
                }
                
                $imageID = Application::instance()->getDB()->addImage( $imageType, $imageName, $mimetype, '', $resolution, $width, $height, $buffer );
                if( $imageID )
                {
                    if( $usedDefaultResolution )
                        $message = "A default resolution of " . $defaultDPI . " dpi was used";
                } else
                    throw new Exception( "The insertion into the database failed." );
                    
                    $insertedImages[] = array (
                        'name' => $imageName,
                        'error' => false,
                        'message' => $message,
                        'id' => $imageID
                    );
                
            } catch( Exception $ex )
            {
                $message = $ex->getMessage();
                if( _DEBUG )
                    $message .= "\n\n(" . unil_get_exception_backtrace( $ex ) . ")";
                    $insertedImages[] = array (
                        'name' => $imageName,
                        'error' => true,
                        'message' => $message
                    );
            }
        }
    }
}

function processImageUploads( $allowMultiple, $type = NULL, $defaultDPI = NULL )
{
    // throw new Exception("GET=\n".var_dump_str($_GET)."\nPOST=\n".var_dump_str($_POST));
    $insertedImages = array ();
    
    if( isset( $_FILES[ 'images' ] ) )
    {
        for( $i = 0; $i < count( $_FILES[ 'images' ] );  ++ $i )
        {
            if( $i >= 1 && ! $allowMultiple )
                break;
            
            if( $_FILES[ 'images' ][ 'error' ][ $i ] == 0 && $_FILES[ 'images' ][ 'size' ][ $i ] > 0 && strlen( $_FILES[ 'images' ][ 'tmp_name' ][ $i ] ) > 0 && strlen( $_FILES[ 'images' ][ 'name' ][ $i ] ) > 0 )
            {
                // Name of the image (not to be confused with temporary uploaded filename)
                $imageName = $_FILES[ 'images' ][ 'name' ][ $i ];
                
                try
                {
                    $filename = $_FILES[ 'images' ][ 'tmp_name' ][ $i ];
                    $message = '';
                    
                    // Try to read the file
                    $handle = fopen( $filename, 'rb' );
                    
                    if( ! $handle )
                        throw new Exception( "The file could not be read." );
                        
                        // Check that the actual size match the received size (unnecessary ?)
                    fseek( $handle, 0, SEEK_END );
                    $size = ftell( $handle );
                    fseek( $handle, 0, SEEK_SET );
                    
                    if( $size !== ( int ) $_FILES[ 'images' ][ 'size' ][ $i ] )
                        throw new Exception( "File sizes mismatch." );
                        
                        // Get the information about the file, since we can't trust the 'type' value given in the
                        // $_FILES variable - it has been set by the client and must not be relied upon.
                        // Bonus question: why is it there in the first place ?
                    
                    $width = NULL;
                    $height = NULL;
                    $resolution = NULL;
                    $mimetype = NULL;
                    $usedDefaultResolution = false;
                    
                    // First, try to read the EXIF meta-data
                    $imagetype = exif_imagetype( $_FILES[ 'images' ][ 'tmp_name' ][ $i ] );
                    
                    if( $imagetype )
                    {
                        // Read the meta-data, turning off PHP warnings because exif_read_data will
                        // anyway return false if the read fails
                        // Bonus question: why does this function issue a warning, then ?
                        $metadata = NULL;
                        try
                        {
                            $metadata = @exif_read_data( $filename, 'FILE', true );
                            // throw new Exception(var_dump_str($metadata));
                        } catch( Exception $e )
                        {
                            // throw new Exception(var_dump_str($e));
                            // It seems that the image (or whatever thing it is) does not contain EXIF meta data.
                            // We'll find another way around using GD and the default resolution given in the
                            // config.
                            
                            // First, if no default resolution has been set up, there's nothing we can do
                            if( ! $defaultDPI )
                                throw $e;
                            
                            $nonExifMetadata = array ();
                            $result = getimagesize( $filename, $nonExifMetadata );
                            
                            if( isset( $result[ 0 ] ) && ( int ) $result[ 0 ] > 0 && isset( $result[ 1 ] ) && ( int ) $result[ 1 ] > 0 && isset( $result[ 'mime' ] ) && strpos( $result[ 'mime' ], 'image' ) === 0 )
                            {
                                $width = ( int ) $result[ 0 ];
                                $height = ( int ) $result[ 1 ];
                                $mimetype = $result[ 'mime' ];
                                $resolution = ( int ) $defaultDPI;
                                $usedDefaultResolution = true;
                            }
                        }
                        
                        if( $metadata )
                        {
                            // Get the mimetype from the FILE section
                            if( array_key_exists( 'FILE', $metadata ) && array_key_exists( 'MimeType', $metadata[ 'FILE' ] ) )
                                $mimetype = $metadata[ 'FILE' ][ 'MimeType' ];
                                
                                // Find the width and height in the COMPUTED section
                            if( array_key_exists( 'COMPUTED', $metadata ) )
                            {
                                if( array_key_exists( 'Width', $metadata[ 'COMPUTED' ] ) )
                                    $width = ( int ) $metadata[ 'COMPUTED' ][ 'Width' ];
                                if( $width !== NULL && $width <= 0 )
                                    $width = NULL;
                                
                                if( array_key_exists( 'Height', $metadata[ 'COMPUTED' ] ) )
                                    $height = ( int ) $metadata[ 'COMPUTED' ][ 'Height' ];
                                if( $height !== NULL && $height <= 0 )
                                    $height = NULL;
                            }
                            
                            // Find the resolution in the XFD0 section
                            if( array_key_exists( 'IFD0', $metadata ) )
                            {
                                $xresolution = NULL;
                                if( array_key_exists( 'XResolution', $metadata[ 'IFD0' ] ) )
                                {
                                    $xresolutionarr = explode( '/', $metadata[ 'IFD0' ][ 'XResolution' ] );
                                    if( count( $xresolutionarr ) == 2 )
                                        $xresolution = ( int ) $xresolutionarr[ 0 ] / ( int ) $xresolutionarr[ 1 ];
                                }
                                if( $xresolution !== NULL && $xresolution <= 0 )
                                    $xresolution = NULL;
                                
                                $yresolution = NULL;
                                if( array_key_exists( 'YResolution', $metadata[ 'IFD0' ] ) )
                                {
                                    $yresolutionarr = explode( '/', $metadata[ 'IFD0' ][ 'YResolution' ] );
                                    if( count( $yresolutionarr ) == 2 )
                                        $yresolution = ( int ) $yresolutionarr[ 0 ] / ( int ) $yresolutionarr[ 1 ];
                                }
                                if( $yresolution !== NULL && $yresolution <= 0 )
                                    $yresolution = NULL;
                                
                                if( $xresolution !== NULL && $yresolution !== NULL )
                                {
                                    if( $xresolution !== $yresolution )
                                        throw new Exception( "X and Y resolutions mismatch (XRes is " . $xresolution . " dpi and YRes is " . $yresolution . " dpi)." );
                                    $resolution = $xresolution;
                                }
                            }
                            
                            // If the resolution is still NULL at this point, it means that the IDF0 section of
                            // the EXIF data was either missing or did not include the resolution definition. At
                            // this point, and if the 'images_default_resolution' setting is defined, we apply it.
                            if( $resolution === NULL && $defaultDPI )
                            {
                                $resolution = ( int ) $defaultDPI;
                                $usedDefaultResolution = true;
                            }
                        }
                    } else
                    {
                        throw new Exception( "The file is not an image, or its type is unknown/unsupported." );
                    }
                    
                    if( ! $width || ! $height || ! $resolution || ! $mimetype )
                    {
                        // No EXIF meta-data was found, or the EXIF did not contain all the information we wanted
                        // TODO: implement a rollback method to fetch the required meta-data through other means
                        // e.g. ImageMagick
                        throw new Exception( "The required meta-data (MIME type, width, height and resolution) was not found." );
                    }
                    
                    if( $type !== NULL )
                        $imageType = $type;
                    elseif( isset( $_POST[ 'types' ] ) && is_array( $_POST[ 'types' ] ) )
                        $imageType = $_POST[ 'types' ][ $i ];
                    else
                        $imageType = $_POST[ 'type' ];
                        
                        // Will hold actual bytes to store in the DB
                    $buffer = NULL;
                    
                    // Perform on-the-fly conversions if needed
                    $convData = Application::get( 'convert_images' );
                    if( $convData )
                    {
                        // throw new Exception(var_dump_str(array($mimetype,$convData)));
                        if( array_key_exists( $mimetype, $convData ) )
                        {
                            fclose( $handle );
                            $buffer = imagick_wrap( 'convert', array (
                                    'filename' => $filename, 
                                    'extension' => $convData[ $mimetype ] 
                            ) );
                            $imageName .= '.' . $convData[ $mimetype ];
                            $mimetype = 'image/' . $convData[ $mimetype ];
                        }
                    }
                    
                    if( ! $buffer )
                    {
                        $buffer = fread( $handle, $size );
                        fclose( $handle );
                    }
                    
                    $imageID = Application::instance()->getDB()->addImage( $imageType, $imageName, $mimetype, '', $resolution, $width, $height, $buffer );
                    if( $imageID )
                    {
                        if( $usedDefaultResolution )
                            $message = "A default resolution of " . $defaultDPI . " dpi was used";
                    } else
                        throw new Exception( "The insertion into the database failed." );
                    
                    $insertedImages[] = array (
                            'name' => $imageName, 
                            'error' => false, 
                            'message' => $message, 
                            'id' => $imageID 
                    );
                } catch( Exception $ex )
                {
                    $message = $ex->getMessage();
                    if( _DEBUG )
                        $message .= "\n\n(" . unil_get_exception_backtrace( $ex ) . ")";
                    $insertedImages[] = array (
                            'name' => $imageName, 
                            'error' => true, 
                            'message' => $message 
                    );
                }
            }
        }
    }
    
    return $insertedImages;
}

// Executes an arbitrary command with passhtru(). The output is put in a temporary buffer and
// returned. Any problem (invalid command, return code != 0, ...) will raise an exception - the
// catch clause is used as a clean-up routine, but the exception will still pass through.
function exec_command( $command )
{
    $content = NULL;
    try
    {
        ob_start();
        $ret = - 1;
        passthru( $command, $ret );
        if( $ret !== 0 )
            throw new Exception( 'Error while executing ImageMagick command: return code ' . $ret );
        $content = ob_get_clean();
        if( ! $content )
            throw new Exception( 'Error while executing ImageMagick command: returned an empty image' );
    } catch( Exception $e )
    {
        while( ob_get_level() )
            ob_end_clean();
        throw $e;
    }
    return $content;
}
function imagick_wrap( $action, $args )
{
    $conf = Application::get( 'imagick' );
    
    if( is_string( $conf ) )
    {
        switch( $action )
        {
            case 'convert':
                $tool = 'convert';
                $args = array (
                        $args[ 'filename' ], 
                        $args[ 'extension' ] . ':-' 
                );
                break;
        }
        
        if( ! isset( $tool ) || ! isset( $args ) )
            throw new Exception( "Assertion failed" );
        
        $program = $conf;
        if( PHP_OS == 'WINNT' )
            $program .= '\\' . $tool . '.exe';
        else
            $program .= '/' . $tool;
        
        $command = $program;
        foreach( $args as $arg )
            $command .= ' ' . escapeshellarg( $arg );
        
        return exec_command( $command );
    } else
    {
        switch( $action )
        {
            case 'convert':
                $image = new Imagick( $args[ 'filename' ] );
                $image->setImageFormat( $args[ 'extension' ] );
                ob_start();
                echo $image;
                return ob_get_clean();
        }
    }
    
    throw new Exception( "Assertion failed" );
}
function sendImage( $id, $isThumb, $isDownload )
{
    $db = Application::instance()->getDB();
    
    // Check the ACL if non admin
    if( ! $_SESSION[ 'user' ][ 'isadmin' ] )
    {
        $res = $db->query( "SELECT checkImageReadACL(" . ( int ) $id . "," . ( int ) $_SESSION[ 'user' ][ 'id' ] . ") AS allowed;" );
        if( $res[ 0 ][ 'allowed' ] !== 't' )
            throw new Exception( "Access denied" );
    }
    
    $info = $db->getImageInfo( $id );
    $imageData = NULL;
    
    // One cannot request a thumbnail ID directly - the ID of the actual image must be
    // given, with the 'thumb' option on
    if( $info[ 'isthumb' ] )
        throw new Exception( 'Access denied' );
        
        // Create an appropriate ETag and last modified date
        // to allow clients to cache the image
    $etag = '"p4img-' . $info[ 'id' ] . '-' . ( $isThumb ? '1': '0' ) . '-' . md5( $info[ 'updatedon' ] ) . '"';
    $cacheLastModified = gmdate( 'D, d M Y H:i:s', $info[ 'updatedon_unix' ] ) . ' GMT'; // Date as RFC 2822, suitable for an HTTP header
                                                                                         
    // Check whether the client already has the latest version of this image
    $send304 = false;
    if( isset( $_SERVER[ 'HTTP_IF_NONE_MATCH' ] ) )
    {
        $client_etag = $_SERVER[ 'HTTP_IF_NONE_MATCH' ];
        if( $client_etag == $etag )
            $send304 = true;
    } elseif( isset( $_SERVER[ 'HTTP_IF_MODIFIED_SINCE' ] ) )
    {
        $d = $_SERVER[ 'HTTP_IF_MODIFIED_SINCE' ];
        if( $d == $cacheLastModified ) // very lazy check for equality, without even parsing the client's date
            $send304 = true;
    }
    
    if( $send304 )
    {
        // Client is up-to-date, just send a 304 Not Modified (but still disabling
        // the headers auto-set by PHP. See comment at the very end of this function)
        header( "HTTP/1.1 304 Not Modified" );
        header( "ETag: " . $etag );
        header( "Cache-Control: private, max-age=86400" );
        header( "Pragma: " );
        header( "Expires: " );
        exit();
    }
    
    if( $isThumb )
    {
        if( ( int ) $info[ 'thumb' ] > 0 )
        {
            // OK, we have found a thumbnail
            $idToSend = ( int ) $info[ 'thumb' ];
        } else
        {
            // No thumbnail found. Let's generate one
            // We will create a 128x128 pixels thumbnail, reducing the image keeping proportions
            // if any dimension is bigger than 128, and simply centering it on a 128x128 canvas
            // otherwise. Space left unoccupied will be filled with the color defined in the
            // configuration file.
            
            // Grab the actual image's data
            $res = $db->query( "SELECT data FROM image WHERE id = " . ( int ) $id . ";" );
            $data = pg_unescape_bytea( $res[ 0 ][ 'data' ] );
            
            $layer = imagecreatefromstring( $data );
            
            // Create the background
            $image = imagecreatetruecolor( 128, 128 );
            
            if( $info[ 'width' ] > 128 or $info[ 'height' ] > 128 )
            {
                // We need to resize, so compute the aspect ratio we want to keep
                $aspect = ( float ) $info[ 'width' ] / ( float ) $info[ 'height' ];
                
                // Compute the dimensions of the image layer
                if( $aspect > 1.0 )
                {
                    $newwidth = 128;
                    $newheight = ( int ) floor( 128.0 / $aspect );
                    $dstx = 0;
                    $dsty = ( 128 - $newheight ) / 2;
                } else
                {
                    $newheight = 128;
                    $newwidth = ( int ) floor( 128.0 * $aspect );
                    $dstx = ( 128 - $newwidth ) / 2;
                    $dsty = 0;
                }
                
                // Copy and resize the image layer
                imagecopyresized( $image, $layer, $dstx, $dsty, 0, 0, $newwidth, $newheight, $info[ 'width' ], $info[ 'height' ] );
            } else
            {
                // We need to copy-paste on the center
                $dstx = 64 - $info[ 'width' ] / 2;
                $dsty = 64 - $info[ 'height' ] / 2;
                imagecopy( $image, $layer, $dstx, $dsty, 0, 0, $info[ 'width' ], $info[ 'height' ] );
            }
            
            // PHP is really the worst thing that was ever invented and will not allow us
            // to output the image to a buffer. We *have* to capture the output using
            // output buffering
            ob_start();
            imagepng( $image );
            $buffer = ob_get_clean();
            
            $db->beginTransaction();
            // Store the image data
            $idToSend = $db->addImage( 'THUMBNAIL', $info[ 'name' ], 'image/png', '', - 1, 128, 128, $buffer );
            // Set the original image's thumb to point here
            $db->query( "UPDATE image SET thumb = " . ( int ) $idToSend . " WHERE id = " . ( int ) $id . ";" );
            // Give ownership to an admin so that the user is not actually the
            // owner of a thumbnail.
            $db->setObjectOwnership( $idToSend );
            $db->commitTransaction();
        }
    } else
    {
        $idToSend = $id;
    }
    
    // Update the info since we might be sending a different id than asked
    if( $idToSend != $id )
        $info = $db->getImageInfo( $idToSend );
    
    $res = $db->query( "SELECT data FROM image WHERE id = " . ( int ) $idToSend . ";" );
    $data = pg_unescape_bytea( $res[ 0 ][ 'data' ] );
    
    // Set the HTTP headers for the content
    header( "Content-Type: " . $info[ 'mimetype' ] );
    header( 'Content-Disposition: ' . ( $isDownload ? 'attachment': 'inline' ) . ';filename="' . $info[ 'name' ] . '"' );
    header( "Content-Length: " . strlen( $data ) );
    // Trigger browser caching by setting the proper Last-Modified and Cache-Control headers
    header( "Last-Modified: " . $cacheLastModified );
    header( "Cache-Control: private, max-age=86400" );
    // Add the ETag to allow quick identity checks
    header( "ETag: " . $etag );
    // Disable these headers that are auto-set by PHP and its session.cache_limiter thingy.
    // We cannot use session_cache_limiter('') here because it's too late - the session has
    // already started.
    header( "Pragma: " );
    header( "Expires: " );
    
    echo $data;
}

?>
