eZ Publish  [trunk]
ezfile.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZFile class.
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package lib
00009  */
00010 
00011 /*!
00012  \class eZFile ezfile.php
00013  \ingroup eZUtils
00014  \brief Tool class which has convencience functions for files and directories
00015 
00016 */
00017 class eZFile
00018 {
00019     /**
00020      * Number of bytes read per fread() operation.
00021      *
00022      * @see downloadContent()
00023      */
00024     const READ_PACKET_SIZE = 16384;
00025 
00026     /**
00027      * Flags for file manipulation
00028      *
00029      * @see rename()
00030      */
00031     const CLEAN_ON_FAILURE = 1,
00032           APPEND_DEBUG_ON_FAILURE = 2;
00033 
00034     /**
00035      * Reads the whole contents of the file \a $file and
00036      * splits it into lines which is collected into an array and returned.
00037      * It will handle Unix (\n), Windows (\r\n) and Mac (\r) style newlines.
00038      * \note The newline character(s) are not present in the line string.
00039      *
00040      * @deprecated Since 4.4, use file( $file, FILE_IGNORE_NEW_LINES ) instead.
00041      * @return array|false
00042      */
00043     static function splitLines( $file )
00044     {
00045         $contents = file_get_contents( $file );
00046         if ( $contents === false )
00047             return false;
00048         $lines = preg_split( "#\r\n|\r|\n#", $contents );
00049         unset( $contents );
00050         return $lines;
00051     }
00052 
00053     /*!
00054      Creates a file called \a $filename.
00055      If \a $directory is specified the file is placed there, the directory will also be created if missing.
00056      if \a $data is specified the file will created with the content of this variable.
00057 
00058      \param $atomic If true the file contents will be written to a temporary file and renamed to the correct file.
00059     */
00060     static function create( $filename, $directory = false, $data = false, $atomic = false )
00061     {
00062         $filepath = $filename;
00063         if ( $directory )
00064         {
00065             if ( !file_exists( $directory ) )
00066             {
00067                 eZDir::mkdir( $directory, false, true );
00068 //                 eZDebugSetting::writeNotice( 'ezfile-create', "Created directory $directory", 'eZFile::create' );
00069             }
00070             $filepath = $directory . '/' . $filename;
00071         }
00072         // If atomic creation is needed we will use a temporary
00073         // file when writing the data, then rename it to the correct path.
00074         if ( $atomic )
00075         {
00076             $realpath = $filepath;
00077             $dirname  = dirname( $filepath );
00078             if ( strlen( $dirname ) != 0 )
00079                 $dirname .= "/";
00080             $filepath = $dirname . "ezfile-tmp." . md5( $filepath . getmypid() . mt_rand() );
00081         }
00082 
00083         $file = fopen( $filepath, 'wb' );
00084         if ( $file )
00085         {
00086 //             eZDebugSetting::writeNotice( 'ezfile-create', "Created file $filepath", 'eZFile::create' );
00087             if ( $data )
00088                 fwrite( $file, $data );
00089             fclose( $file );
00090 
00091             if ( $atomic )
00092             {
00093                 // If the renaming process fails, delete the temporary file
00094                 eZFile::rename( $filepath, $realpath, false, eZFile::CLEAN_ON_FAILURE );
00095             }
00096             return true;
00097         }
00098 //         eZDebugSetting::writeNotice( 'ezfile-create', "Failed creating file $filepath", 'eZFile::create' );
00099         return false;
00100     }
00101 
00102     /*!
00103      \static
00104      Read all content of file.
00105 
00106      \param filename
00107 
00108      \return file contents, false if error
00109 
00110      \deprecated since eZ Publish 4.1, use file_get_contents() instead
00111     */
00112     static function getContents( $filename )
00113     {
00114         eZDebug::writeWarning( __METHOD__ . ' is deprecated, use file_get_contents() instead' );
00115 
00116         if ( function_exists( 'file_get_contents' ) )
00117         {
00118             return file_get_contents( $filename );
00119         }
00120         else
00121         {
00122             $fp = fopen( $filename, 'r' );
00123             if ( !$fp )
00124             {
00125                 eZDebug::writeError( 'Could not read contents of ' . $filename, __METHOD__ );
00126                 return false;
00127             }
00128 
00129             return fread( $fp, filesize( $filename ) );
00130         }
00131     }
00132 
00133     /*!
00134      \static
00135      Get suffix from filename
00136 
00137      \param filename
00138      \return suffix, extends: file/to/readme.txt return txt
00139     */
00140     static function suffix( $filename )
00141     {
00142         $parts = explode( '.', $filename);
00143         return array_pop( $parts );
00144     }
00145 
00146     /*!
00147     \static
00148     Check if a given file is writeable
00149 
00150     \return TRUE/FALSE
00151     */
00152     static function isWriteable( $filename )
00153     {
00154         if ( eZSys::osType() != 'win32' )
00155             return is_writable( $filename );
00156 
00157         /* PHP function is_writable() doesn't work correctly on Windows NT descendants.
00158          * So we have to use the following hack on those OSes.
00159          */
00160         if ( !( $fd = @fopen( $filename, 'a' ) ) )
00161             return FALSE;
00162 
00163         fclose( $fd );
00164 
00165         return TRUE;
00166     }
00167 
00168     /**
00169      * Renames $srcFile to $destFile atomically on Unix, and provides a workaround for Windows.
00170      *
00171      * Usage example:
00172      * <code>
00173      * $srcFile = '/path/to/src/file';
00174      * $destFile = '/path/to/dest/file';
00175      * eZFile::rename( $srcFile, $destFile );
00176      *
00177      * // Using flags
00178      * // In following example, if rename operation fails, $srcFile will be deleted and a message will be appended in eZDebug
00179      * eZFile::rename( $srcFile, $destFile, false, eZFile::APPEND_DEBUG_ON_FAILURE | eZFile::CLEAN_ON_FAILURE );
00180      * </code>
00181      *
00182      * @param string $srcFile Source file path
00183      * @param string $destFile Destination file path
00184      * @param bool $mkdir Make directory for destination file if needed
00185      * @param int $flags Supported flags are :
00186      *                     - APPEND_DEBUG_ON_FAILURE (will append a message to the debug if operation fails
00187      *                     - CLEAN_ON_FAILURE (Will remove $srcFile if operation fails)
00188      * @return bool rename() status (true if successful, false if not)
00189      */
00190     static function rename( $srcFile, $destFile, $mkdir = false, $flags = 0 )
00191     {
00192         /* On windows we need to unlink the destination file first */
00193         if ( strtolower( substr( PHP_OS, 0, 3 ) ) == 'win' )
00194         {
00195             @unlink( $destFile );
00196         }
00197         if( $mkdir )
00198         {
00199             eZDir::mkdir( dirname( $destFile ), false, true );
00200         }
00201 
00202         $status = rename( $srcFile, $destFile );
00203         // Rename operation failed, check $flags to know what to do then
00204         if ( $status === false )
00205         {
00206             if ( $flags & self::APPEND_DEBUG_ON_FAILURE )
00207                 eZDebug::writeWarning( "$srcFile could not be renamed to $destFile", __METHOD__ );
00208 
00209             if ( $flags & self::CLEAN_ON_FAILURE )
00210                 unlink( $srcFile );
00211         }
00212 
00213         return $status;
00214     }
00215 
00216     /**
00217      * Prepares a file for Download and terminates the execution.
00218      * This method will:
00219      * - empty the output buffer
00220      * - stop buffering
00221      * - stop the active session (in order to allow concurrent browsing while downloading)
00222      *
00223      * @param string $file Path to the local file
00224      * @param bool $isAttachedDownload Determines weather to download the file as an attachment ( download popup box ) or not.
00225      * @param string $overrideFilename
00226      * @param int $startOffset Offset to start transfer from, in bytes
00227      * @param int $length Data size to transfer
00228      *
00229      * @return bool false if error
00230      */
00231     static function download( $file, $isAttachedDownload = true, $overrideFilename = false, $startOffset = 0, $length = false )
00232     {
00233         if ( !file_exists( $file ) )
00234         {
00235             return false;
00236         }
00237 
00238         ob_end_clean();
00239         eZSession::stop();
00240         self::downloadHeaders( $file, $isAttachedDownload, $overrideFilename, $startOffset, $length );
00241         self::downloadContent( $file, $startOffset, $length );
00242 
00243         eZExecution::cleanExit();
00244     }
00245 
00246     /**
00247      * Handles the header part of a file transfer to the client
00248      *
00249      * @see download()
00250      *
00251      * @param string $file Path to the local file
00252      * @param bool $isAttachedDownload Determines weather to download the file as an attachment ( download popup box ) or not.
00253      * @param string $overrideFilename Filename to send in headers instead of the actual file's name
00254      * @param int $startOffset Offset to start transfer from, in bytes
00255      * @param int $length Data size to transfer
00256      * @param string $fileSize The file's size. If not given, actual filesize will be queried. Required to work with clusterized files...
00257      */
00258     public static function downloadHeaders( $file, $isAttachedDownload = true, $overrideFilename = false, $startOffset = 0, $length = false, $fileSize = false )
00259     {
00260         if ( $fileSize === false )
00261         {
00262             if ( !file_exists( $file ) )
00263             {
00264                 eZDebug::writeError( "\$fileSize not given, and file not found", __METHOD__ );
00265                 return false;
00266             }
00267 
00268             $fileSize = filesize( $file );
00269         }
00270 
00271         header( 'X-Powered-By: eZ Publish' );
00272         $mimeinfo = eZMimeType::findByURL( $file );
00273         header( "Content-Type: {$mimeinfo['name']}" );
00274 
00275         // Fixes problems with IE when opening a file directly
00276         header( "Pragma: " );
00277         header( "Cache-Control: " );
00278         // Last-Modified header cannot be set, otherwise browser like FF will fail while resuming a paused download
00279         // because it compares the value of Last-Modified headers between requests.
00280         header( "Last-Modified: " );
00281         /* Set cache time out to 10 minutes, this should be good enough to work
00282            around an IE bug */
00283         header( "Expires: ". gmdate( 'D, d M Y H:i:s', time() + 600 ) . ' GMT' );
00284         header(
00285             "Content-Disposition: " .
00286             ( $isAttachedDownload ? 'attachment' : 'inline' ) .
00287             ( $overrideFilename !== false ? "; filename={$overrideFilename}" : '' )
00288         );
00289 
00290         // partial download (HTTP 'Range' header)
00291         if ( $startOffset !== 0 )
00292         {
00293             $endOffset = ( $length !== false ) ? ( $length + $startOffset - 1 ) : $fileSize - 1;
00294             header( "Content-Length: " . ( $endOffset - $startOffset + 1 ) );
00295             header( "Content-Range: bytes {$startOffset}-{$endOffset}/{$fileSize}" );
00296             header( "HTTP/1.1 206 Partial Content" );
00297         }
00298         else
00299         {
00300             header( "Content-Length: $fileSize" );
00301         }
00302         header( 'Content-Transfer-Encoding: binary' );
00303         header( 'Accept-Ranges: bytes' );
00304     }
00305 
00306     /**
00307      * Handles the data part of a file transfer to the client
00308      *
00309      * @see download()
00310      *
00311      * @param string $file Path to the local file
00312      * @param int $startOffset Offset to start transfer from, in bytes
00313      * @param int $length Data size to transfer
00314      */
00315     public static function downloadContent( $file, $startOffset = 0, $length = false )
00316     {
00317         if ( !file_exists( $file ) )
00318         {
00319             eZDebug::writeError( "'$file' does not exist", __METHOD__ );
00320             return false;
00321         }
00322         if ( ( $fp = fopen( $file, 'rb' ) ) === false )
00323         {
00324             eZDebug::writeError( "An error occured opening '$file' for reading", __METHOD__ );
00325             return false;
00326         }
00327 
00328         $fileSize = filesize( $file );
00329 
00330         // an offset has been given: move the pointer to that offset if it seems valid
00331         if ( $startOffset !== false && $startOffset <= $fileSize && fseek( $fp, $startOffset ) === -1 )
00332         {
00333             eZDebug::writeError( "Error while setting offset on '{$file}'", __METHOD__ );
00334             return false;
00335         }
00336 
00337         $transferred = $startOffset;
00338         $packetSize = self::READ_PACKET_SIZE;
00339         $endOffset = ( $length === false ) ? $fileSize - 1 : $length + $startOffset - 1;
00340 
00341         while ( !feof( $fp ) && $transferred < $endOffset + 1 )
00342         {
00343             if ( $transferred + $packetSize > $endOffset + 1 )
00344             {
00345                 $packetSize = $endOffset + 1 - $transferred;
00346             }
00347             echo fread( $fp, $packetSize );
00348             $transferred += $packetSize;
00349         }
00350         fclose( $fp );
00351 
00352         return true;
00353     }
00354 }
00355 
00356 ?>