* @author Kornel LesiƄski * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License * @version SVN: $Id$ * @link http://phptal.org/ */ /** * @package PHPTAL * @subpackage Php */ class PHPTAL_Php_State { private $debug = false; private $tales_mode = 'tales'; private $encoding; private $output_mode; private $phptal; function __construct(PHPTAL $phptal) { $this->phptal = $phptal; $this->encoding = $phptal->getEncoding(); $this->output_mode = $phptal->getOutputMode(); } /** * used by codewriter to get information for phptal:cache */ public function getCacheFilesBaseName() { return $this->phptal->getCodePath(); } /** * true if PHPTAL has translator set */ public function isTranslationOn() { return !!$this->phptal->getTranslator(); } /** * controlled by phptal:debug */ public function setDebug($bool) { $old = $this->debug; $this->debug = $bool; return $old; } /** * if true, add additional diagnostic information to generated code */ public function isDebugOn() { return $this->debug; } /** * Sets new and returns old TALES mode. * Valid modes are 'tales' and 'php' * * @param string $mode * * @return string */ public function setTalesMode($mode) { $old = $this->tales_mode; $this->tales_mode = $mode; return $old; } public function getTalesMode() { return $this->tales_mode; } /** * encoding used for both template input and output */ public function getEncoding() { return $this->encoding; } /** * Syntax rules to follow in generated code * * @return one of PHPTAL::XHTML, PHPTAL::XML, PHPTAL::HTML5 */ public function getOutputMode() { return $this->output_mode; } /** * Load prefilter */ public function getPreFilterByName($name) { return $this->phptal->getPreFilterByName($name); } /** * compile TALES expression according to current talesMode * @return string with PHP code or array with expressions for TalesChainExecutor */ public function evaluateExpression($expression) { if ($this->getTalesMode() === 'php') { return PHPTAL_Php_TalesInternal::php($expression); } return PHPTAL_Php_TalesInternal::compileToPHPExpressions($expression, false); } /** * compile TALES expression according to current talesMode * @return string with PHP code */ private function compileTalesToPHPExpression($expression) { if ($this->getTalesMode() === 'php') { return PHPTAL_Php_TalesInternal::php($expression); } return PHPTAL_Php_TalesInternal::compileToPHPExpression($expression, false); } /** * returns PHP code that generates given string, including dynamic replacements * * It's almost unused. */ public function interpolateTalesVarsInString($string) { return PHPTAL_Php_TalesInternal::parseString($string, false, ($this->getTalesMode() === 'tales') ? '' : 'php:' ); } /** * replaces ${} in string, expecting HTML-encoded input and HTML-escapes output */ public function interpolateTalesVarsInHTML($src) { return preg_replace_callback('/((?:\$\$)*)\$\{(structure |text )?(.*?)\}|((?:\$\$)+)\{/isS', array($this,'_interpolateTalesVarsInHTMLCallback'), $src); } /** * callback for interpolating TALES with HTML-escaping */ private function _interpolateTalesVarsInHTMLCallback($matches) { return $this->_interpolateTalesVarsCallback($matches, 'html'); } /** * replaces ${} in string, expecting CDATA (basically unescaped) input, * generates output protected against breaking out of CDATA in XML/HTML * (depending on current output mode). */ public function interpolateTalesVarsInCDATA($src) { return preg_replace_callback('/((?:\$\$)*)\$\{(structure |text )?(.*?)\}|((?:\$\$)+)\{/isS', array($this,'_interpolateTalesVarsInCDATACallback'), $src); } /** * callback for interpolating TALES with CDATA escaping */ private function _interpolateTalesVarsInCDATACallback($matches) { return $this->_interpolateTalesVarsCallback($matches, 'cdata'); } private function _interpolateTalesVarsCallback($matches, $format) { // replaces $${ with literal ${ (or $$$${ with $${ etc) if (!empty($matches[4])) { return substr($matches[4], strlen($matches[4])/2).'{'; } // same replacement, but before executed expression $dollars = substr($matches[1], strlen($matches[1])/2); $code = $matches[3]; if ($format == 'html') { $code = html_entity_decode($code, ENT_QUOTES, $this->getEncoding()); } $code = $this->compileTalesToPHPExpression($code); if (rtrim($matches[2]) == 'structure') { // regex captures a space there return $dollars.'stringify($code)." ?>\n"; } else { if ($format == 'html') { return $dollars.'htmlchars($code)." ?>\n"; } if ($format == 'cdata') { // quite complex for an "unescaped" section, isn't it? if ($this->getOutputMode() === PHPTAL::HTML5) { return $dollars."stringify($code).") ?>\n"; } elseif ($this->getOutputMode() === PHPTAL::XHTML) { // both XML and HMTL, because people will inevitably send it as text/html :( return $dollars."stringify($code)." ,array(']]>'=>']]]]>',''<\\/')) ?>\n"; } else { return $dollars."',']]]]>', ".$this->stringify($code).") ?>\n"; } } assert(0); } } /** * expects PHP code and returns PHP code that will generate escaped string * Optimizes case when PHP string is given. * * @return php code */ public function htmlchars($php) { // PHP strings can be escaped at compile time if (preg_match('/^\'((?:[^\'{]+|\\\\.)*)\'$/s', $php, $m)) { return "'".htmlspecialchars(str_replace('\\\'', "'", $m[1]), ENT_QUOTES, $this->encoding)."'"; } return 'phptal_escape('.$php.', \''.$this->encoding.'\')'; } /** * allow proper printing of any object * (without escaping - for use with structure keyword) * * @return php code */ public function stringify($php) { // PHP strings don't need to be changed if (preg_match('/^\'(?>[^\'\\\\]+|\\\\.)*\'$|^\s*"(?>[^"\\\\]+|\\\\.)*"\s*$/s', $php)) { return $php; } return 'phptal_tostring('.$php.')'; } }