eZ Publish  [trunk]
ezcodetemplate.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZCodeTemplate 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 kernel
00009  */
00010 
00011 /*!
00012   \class eZCodeTemplate ezcodetemplate.php
00013   \brief Replaces or generates blocks of code according to a template file
00014 
00015 */
00016 
00017 class eZCodeTemplate
00018 {
00019     /// There are errors in the template code
00020     const STATUS_FAILED = 0;
00021 
00022     /// Code files was succesfully updated
00023     const STATUS_OK = 1;
00024 
00025     /// Code file was updated, but no new elements has been added
00026     const STATUS_NO_CHANGE = 2;
00027 
00028     /*!
00029      Constructor
00030     */
00031     function eZCodeTemplate()
00032     {
00033         $ini = eZINI::instance( 'codetemplate.ini' );
00034         $this->Templates = array();
00035         $templates = $ini->variable( 'Files', 'Templates' );
00036         foreach ( $templates as $key => $template )
00037         {
00038             $this->Templates[$key] = array( 'filepath' => $template );
00039         }
00040 
00041         // The default limit has to be increased to avoid PREG_BACKTRACK_LIMIT_ERROR
00042         // when calling preg_*() functions.
00043         ini_set( "pcre.backtrack_limit", 1e6);
00044     }
00045 
00046     /*!
00047       Applies template block in the file \a $filePath and writes back the new
00048       code to the same file.
00049 
00050       \return One of the EZ_CODE_TEMPLATE_STATUS_* status codes.
00051 
00052       \note It will create a backup file of the original
00053     */
00054     function apply( $filePath, $checkOnly = false )
00055     {
00056         if ( !file_exists( $filePath ) )
00057         {
00058             eZDebug::writeError( "File $filePath does not exist", __METHOD__ );
00059             return self::STATUS_FAILED;
00060         }
00061 
00062         $text = file_get_contents( $filePath );
00063         $tempFile = dirname( $filePath ) . '/#' . basename( $filePath ) . '#';
00064         $fd = fopen( $tempFile, 'wb' );
00065         if ( !$fd )
00066         {
00067             eZDebug::writeError( "Failed to open temporary file $tempFile", __METHOD__ );
00068             return self::STATUS_FAILED;
00069         }
00070 
00071         $createTag = 'code-template::create-block:';
00072         $createTagLen = strlen( $createTag );
00073 
00074         $error = false;
00075 
00076         $ok = true;
00077         $offset = 0;
00078         $len = strlen( $text );
00079         while ( $offset < $len )
00080         {
00081             $createPos = strpos( $text, $createTag, $offset );
00082             if ( $createPos !== false )
00083             {
00084                 $endPos = strpos( $text, "\n", $createPos + $createTagLen );
00085                 if ( $endPos === false )
00086                 {
00087                     $createText = substr( $text, $createPos + $createTagLen );
00088                     $end = $len;
00089                 }
00090                 else
00091                 {
00092                     $start = $createPos + $createTagLen;
00093                     $createText = substr( $text, $start, $endPos - $start );
00094                     $end =  $endPos + 1;
00095                 }
00096 
00097                 // Figure out how much the comments should be indented
00098                 // This just makes the code seem more natural
00099                 $indentText = '';
00100                 $subText = substr( $text, $offset, $createPos - $offset );
00101                 $startOfLine = strrpos( $subText, "\n" );
00102                 if ( $startOfLine !== false )
00103                 {
00104                     if ( preg_match( '#^([ \t]+)#', substr( $subText, $startOfLine + 1 ), $matches ) )
00105                     {
00106                         $indentText = $matches[1];
00107                     }
00108                 }
00109                 unset( $subText );
00110 
00111                 // Figure out template name and parameters
00112                 $createText = trim( $createText );
00113                 $elements = explode( ',', $createText );
00114                 if ( count( $elements ) < 1 )
00115                 {
00116                     eZDebug::writeError( "No template name found in file $filePath at offset $offset", __METHOD__ );
00117                     $offset = $end;
00118                     $error = true;
00119                     continue;
00120                 }
00121 
00122                 $templateName = trim( $elements[0] );
00123 
00124                 $templateFile = $this->templateFile( $templateName );
00125                 if ( $templateFile === false )
00126                 {
00127                     eZDebug::writeError( "No template file for template $templateName used in file $filePath at offset $offset", __METHOD__ );
00128                     $offset = $end;
00129                     $error = true;
00130                     continue;
00131                 }
00132 
00133                 if ( !file_exists( $templateFile ) )
00134                 {
00135                     eZDebug::writeError( "Template file $templateFile for template $templateName does not exist", __METHOD__ );
00136                     $offset = $end;
00137                     $error = true;
00138                     continue;
00139                 }
00140 
00141                 $parameters = array_splice( $elements, 1 );
00142                 foreach ( $parameters as $key => $parameter )
00143                 {
00144                     $parameters[$key] = trim( $parameter );
00145                 }
00146 
00147                 if ( !file_exists( $templateFile ) )
00148                 {
00149                     eZDebug::writeError( "Template file $templateFile was not found while workin on $filePath at offset $offset", __METHOD__ );
00150                     $offset = $end;
00151                     $error = true;
00152                     continue;
00153                 }
00154 
00155                 // Load the template file and split it into the blocks
00156                 // available blocks in the file
00157                 $templateText = file_get_contents( $templateFile );
00158 
00159                 $tagSplit = '#((?:<|/\*)(?:START|END):code-template::(?:[a-zA-Z]+[a-zA-Z0-9_|&-]*)(?:>|\*/)[\n]?)#';
00160                 $tagRegexp = '#(?:<|/\*)(START|END):code-template::([a-zA-Z]+[a-zA-Z0-9_|&-]*)[\n]?(?:>|\*/)#';
00161 
00162                 $split = preg_split( $tagSplit, $templateText, -1, PREG_SPLIT_DELIM_CAPTURE );
00163 
00164                 $currentBlocks = array();
00165                 $blocks = array();
00166                 $currentTag = false;
00167                 for ( $i = 0; $i < count( $split ); ++$i )
00168                 {
00169                     $part = $split[$i];
00170                     if ( ( $i % 2 ) == 1 )
00171                     {
00172                         // The tag element
00173                         if ( $currentTag === false )
00174                         {
00175                             preg_match( $tagRegexp, trim( $part ), $matches );
00176                             $currentTag = $matches[2];
00177                             if ( $matches[1] == 'END' )
00178                             {
00179                                 eZDebug::writeError( "Tag $currentTag was finished before it was started, skipping it", __METHOD__ );
00180                                 $currentTag = false;
00181                                 $error = true;
00182                             }
00183                             else
00184                             {
00185                                 if ( count( $currentBlocks ) > 0 )
00186                                     $blocks[] = array( 'blocks' => $currentBlocks );
00187                                 $currentBlocks = array();
00188                             }
00189                         }
00190                         else
00191                         {
00192                             preg_match( $tagRegexp, trim( $part ), $matches );
00193                             $tag = $matches[2];
00194                             if ( $matches[1] == 'END' )
00195                             {
00196                                 if ( $tag == $currentTag )
00197                                 {
00198                                     if ( count( $currentBlocks ) > 0 )
00199                                         $blocks[] = array( 'tag' => $currentTag,
00200                                                            'blocks' => $currentBlocks );
00201                                     $currentTag = false;
00202                                     $currentBlocks = array();
00203                                 }
00204                                 else
00205                                 {
00206                                     eZDebug::writeError( "End tag $tag does not match start tag $currentTag, skipping it", __METHOD__ );
00207                                     $error = true;
00208                                 }
00209                             }
00210                             else
00211                             {
00212                                 eZDebug::writeError( "Start tag $tag found while $currentTag is active, skipping it", __METHOD__ );
00213                                 $error = true;
00214                             }
00215                         }
00216                     }
00217                     else
00218                     {
00219                         // Plain text
00220                         $currentBlocks[] = $part;
00221                     }
00222                 }
00223                 if ( $currentTag === false )
00224                 {
00225                     if ( count( $currentBlocks ) > 0 )
00226                         $blocks[] = array( 'blocks' => $currentBlocks );
00227                 }
00228                 else
00229                 {
00230                     if ( count( $currentBlocks ) > 0 )
00231                         $blocks[] = array( 'tag' => $currentTag,
00232                                            'blocks' => $currentBlocks );
00233                 }
00234 
00235                 // Build new code with blocks to include
00236                 $resultText = '';
00237                 foreach ( $blocks as $block )
00238                 {
00239                     if ( isset( $block['tag'] ) )
00240                     {
00241                         $tagText = $block['tag'];
00242                         if ( strpos( $tagText, '&' ) !== false )
00243                         {
00244                             $tags = explode( '&', $tagText );
00245                             // Check if all tags are present in parameters (and match)
00246                             if ( count( array_intersect( $parameters, $tags ) ) == count( $tags ) )
00247                             {
00248                                 $resultText .= implode( '', $block['blocks'] );
00249                             }
00250                         }
00251                         else if ( strpos( $tagText, '|' ) !== false )
00252                         {
00253                             $tags = explode( '|', $tagText );
00254                             // Check if at least one tag is present in parameters (or match)
00255                             if ( count( array_intersect( $parameters, $tags ) ) == count( $tags ) )
00256                             {
00257                                 $resultText .= implode( '', $block['blocks'] );
00258                             }
00259                         }
00260                         else
00261                         {
00262                             if ( in_array( $tagText, $parameters ) )
00263                             {
00264                                 $resultText .= implode( '', $block['blocks'] );
00265                             }
00266                         }
00267                     }
00268                     else
00269                     {
00270                         $resultText .= implode( '', $block['blocks'] );
00271                     }
00272                 }
00273 
00274                 // Remove any end-of-line whitespaces unless keep-whitespace is used
00275                 if ( !in_array( 'keep-whitespace', $parameters ) )
00276                     $resultText = preg_replace( '#[ \t]+$#m', '', $resultText );
00277 
00278                 // Output text before the template-block
00279                 fwrite( $fd, substr( $text, $offset, $createPos - $offset ) );
00280                 fwrite( $fd, substr( $text, $createPos, $end - $createPos ) );
00281 
00282                 $offset = $end;
00283 
00284                 // Remove any existing auto-generated code
00285                 $autogenRegexp = '#^[ \t]*// code-template::auto-generated:START ' . $templateName . '.+[ \t]*// code-template::auto-generated:END ' . $templateName . '\n#ms';
00286                 $postText = substr( $text, $offset );
00287                 $postText = preg_replace( $autogenRegexp, '', $postText );
00288                 $text = substr( $text, 0, $offset ) . $postText;
00289                 unset( $postText );
00290 
00291                 // Output the template code with markers
00292                 fwrite( $fd, ( "$indentText// code-template::auto-generated:START $templateName\n" .
00293                                "$indentText// This code is automatically generated from $templateFile\n" .
00294                                "$indentText// DO NOT EDIT THIS CODE DIRECTLY, CHANGE THE TEMPLATE FILE INSTEAD\n" .
00295                                "\n" ) );
00296 
00297                 fwrite( $fd, $resultText );
00298                 fwrite( $fd, ( "\n$indentText// This code is automatically generated from $templateFile\n" .
00299                                "$indentText// code-template::auto-generated:END $templateName\n" ) );
00300 
00301             }
00302             else
00303             {
00304                 fwrite( $fd, substr( $text, $offset ) );
00305                 break;
00306             }
00307         }
00308 
00309         fclose( $fd );
00310         if ( !$error )
00311         {
00312             $originalMD5 = md5_file( $filePath );
00313             $updatedMD5 = md5_file( $tempFile );
00314             if ( $originalMD5 == $updatedMD5 )
00315             {
00316                 unlink( $tempFile );
00317                 return self::STATUS_NO_CHANGE;
00318             }
00319             else if ( $checkOnly )
00320             {
00321                 unlink( $tempFile );
00322                 return self::STATUS_OK;
00323             }
00324             else
00325             {
00326                 $backupFile = $filePath . eZSys::backupFilename();
00327                 // Make a backup and make the temporary file the real one
00328                 if ( file_exists( $backupFile ) )
00329                     unlink( $backupFile );
00330                 rename( $filePath, $backupFile );
00331                 rename( $tempFile, $filePath );
00332                 return self::STATUS_OK;
00333             }
00334         }
00335         unlink( $tempFile );
00336         return self::STATUS_FAILED;
00337     }
00338 
00339     /*!
00340      \return The name of the template file based on the name \a $templateName
00341              or \c false if no file is defined for the name.
00342     */
00343     function templateFile( $templateName )
00344     {
00345         if ( isset( $this->Templates[$templateName] ) )
00346         {
00347             return $this->Templates[$templateName]['filepath'];
00348         }
00349         return false;
00350     }
00351 
00352     /*!
00353      \static
00354      Finds all PHP files which must be updated and returns them as an array.
00355 
00356      The files are defined in \c codetemplate.ini in the variable \c PHPFiles
00357     */
00358     function allCodeFiles()
00359     {
00360         $ini = eZINI::instance( 'codetemplate.ini' );
00361         return $ini->variable( 'Files', 'PHPFiles' );
00362     }
00363 
00364     /// \privatesection
00365     public $Templates;
00366 }
00367 
00368 ?>