|
eZ Publish
[trunk]
|
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 ?>