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