blob: 2706ef1dd997783c57ce534c7c0d5c89f2452c88 [file] [log] [blame]
<?php
/**
* File containing the ezcTemplateFunctions class
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Template
* @version //autogen//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @access private
*/
/**
* This class translates Template functions to PHP functions. If the function
* could not directly mapped to a PHP internal function, it calls a static
* function from the Template component, implementing the desired behavior.
*
* @package Template
* @version //autogen//
* @access private
*/
class ezcTemplateFunctions
{
/**
* Keeps the translations from a specific regular expression match to a class name.
* E.g. Everything that starts with "str_" is translated to ezcTemplateStringFunctions.
*
* The 'functions_to_class.php' is read for the translations.
*
* @var array(string=>string) $functionToClass
*/
protected $functionToClass;
/**
* Keeps a reference to the Template parser.
*
* @var ezcTemplateParser $parser
*/
private $parser = null;
/**
* Constructs a new ezcTemplateFunctions
*
* @param ezcTemplateParser $parser
*/
public function __construct( ezcTemplateParser $parser )
{
$this->parser = $parser;
$this->functionToClass = include( "function_to_class.php" );
}
/**
* Returns (a part of) an AST-tree that makes a function call to the function $name with
* the parameters $parameters.
*
* @param string $name
* @param array(ezcTemplateAstNode) $parameters
* @return array(ezcTemplateAstNode)
*/
protected static function functionCall( $name, $parameters )
{
return array( "ezcTemplateFunctionCallAstNode", array( $name, array( "_array", $parameters ) ) );
}
/**
* Returns an Literal AST-node with the value $val.
*
* @param mixed $val
* @return array(ezcTemplateAstNode)
*/
protected static function value( $val )
{
return array( "ezcTemplateLiteralAstNode", array( $val ) );
}
/**
* Returns an Constant AST-node with the value $val.
*
* @param mixed $val
* @return array(ezcTemplateAstNode)
*/
protected static function constant( $val )
{
return array( "ezcTemplateConstantAstNode", array( $val ) );
}
/**
* Concats (with ezcTemplateConcatOperatorAstNode) the two AST trees: $left and $right together.
*
* @param ezcTemplateAstNode $left
* @param ezcTemplateAstNode $right
* @return array(ezcTemplateAstNode)
*/
protected static function concat( $left, $right )
{
return array( "ezcTemplateConcatOperatorAstNode", array( $left, $right ) );
}
/**
* Returns true if the given parameter $parameter should be substituted with an ezcTemplateAstNode.
*
* @param string $parameter
* @return bool
*/
protected static function isSubstitution( $parameter )
{
return ( strpos( $parameter, "%" ) !== false );
}
/**
* Returns true if the given parameter is optional.
*
* @param string $parameter
* @return bool
*/
protected static function isOptional( $parameter )
{
return $parameter[0] == "[";
}
/**
* Returns true if the given parameter represents one or more parameters.
*
* @param string $parameter
* @return bool
*/
protected static function isMany( $parameter )
{
return ( $parameter[1] == "." && $parameter[2] == "." );
}
/**
* Returns the given amount of parameters plus the optional remaining parameters.
*
* @param array(string) $parameters
* @return int
*/
protected static function countParameters( $parameters )
{
$total = sizeof( $parameters );
if ( isset( $parameterMap[ "__rest__" ] ) )
{
$total += sizeof( $parametersMap["__rest__"] ) - 1;
}
return $total;
}
/**
* Returns true if the given AST node is a variable; thus a value can be assigned.
*
* @param ezcTemplateAstNode $ast
* @return bool
*/
protected static function isVariable( $ast )
{
if ( $ast instanceof ezcTemplateVariableAstNode ||
$ast instanceof ezcTemplateReferenceOperatorAstNode ||
$ast instanceof ezcTemplateArrayFetchOperatorAstNode )
{
return true;
}
return false;
}
/**
* Checks the type (currently only Variable) for a given AST node.
* If the type doesn't match, an exception is thrown.
*
* @param string $defined
* @param ezcTemplateAstNode $givenAst
* @param string $functionName Only important for the error message.
* @param int $parameterNumber Only important for the error message.
*
* @throws ezcTemplateException if the function type is incorrect.
*
* @return void
*/
protected static function checkType( $defined, $givenAst, $functionName, $parameterNumber )
{
$s = explode( ":", $defined );
$type = sizeof( $s ) > 1 ? $s[1] : null;
if ( $type !== null && $type == "Variable" )
{
if ( !self::isVariable( $givenAst ) )
{
throw new ezcTemplateException( "The function '$functionName' expects parameter ". ($parameterNumber + 1)." to be a variable." );
}
}
}
/**
* Creates a new instantance of the given $className string with the parameters $functionParameters.
*
* If the class name is equal to "_array", it will just return the $functionParameters.
*
* @param string $className
* @param ezcTemplateAstNode $functionParameters
* @return ezcTemplateAstNode
*/
protected function createObject( $className, $functionParameters )
{
if ( $className == "_array" )
{
return $functionParameters;
}
return call_user_func_array(
array( new ReflectionClass( $className ), 'newInstance' ), $functionParameters );
}
/**
* Translates a function definition to a real AST tree. The function definition contains
* some virtual nodes that needs to be translated.
*
* @param string $functionName
* @param ezcTemplateAstNode $struct
* @param array(string=>ezcTemplateAstNode) $parameterMap
* @param bool $usedRest Initially set to false and may be set to true.
* @return ezcTemplateAstNode
*/
protected function processAst( $functionName, $struct, $parameterMap, &$usedRest )
{
$pOut = array();
foreach ( $struct[1] as $pIn )
{
if ( is_array( $pIn ) )
{
$pOut[] = $this->processAst( $functionName, $pIn, $parameterMap, $usedRest );
}
else
{
if ( self::isSubstitution( $pIn ) )
{
// Skip the optional parameter if the value is null.
if ( $parameterMap[$pIn] !== null )
{
$pOut[] = $parameterMap[ $pIn ];
if ( self::isMany( $pIn ) && isset( $parameterMap[ "__rest__" ] ) )
{
$usedRest = true;
foreach ( $parameterMap[ "__rest__" ] as $restParameter )
{
$pOut[] = $restParameter;
}
}
}
elseif ( !self::isOptional( $pIn ) )
{
throw new ezcTemplateException( sprintf( ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_PARAMETER, $functionName, $pIn ) );
}
}
else
{
// No substitution needed.
$pOut[] = $pIn;
}
}
}
return $this->createObject( $struct[0], $pOut );
}
/**
* Translates a function definition to a real AST tree. The function definition contains
* some virtual nodes that needs to be translated.
*
* @param string $functionName
* @param array(mixed) $functionDefinition
* @param array(ezcTemplateAstNode) $processedParameters
* @return ezcTemplateAstNode
*/
protected function createAstNodes( $functionName, $functionDefinition, $processedParameters )
{
$index = sizeof( $functionDefinition ) == 3 ? 1 : 0;
$realParameters = sizeof( $processedParameters );
$definedParameters = self::countParameters( $functionDefinition[ $index ] );
// Map the parameters from the function definition to the given (real) parameters.
$parameterMap = array();
$i = 0;
foreach ( $functionDefinition[ $index ] as $p )
{
if ( self::isOptional( $p ) && $realParameters < $i )
{
// We should skip this parameter.
$parameterMap[$p] = null;
}
else
{
$parameterMap[$p] = null;
if ( isset( $processedParameters[$i] ) )
{
$parameterMap[$p] = $processedParameters[$i];
self::checkType( $p, $processedParameters[$i], $functionName, $i );
}
$i++;
}
}
// Remaining parameters are stored in the "__rest__".
$hasRest = false;
while ( isset( $processedParameters[$i] ) )
{
$parameterMap[ "__rest__" ][] = $processedParameters[$i++];
$hasRest = true;
}
$usedRest = false;
$ast = $this->processAst( $functionName, $functionDefinition[ $index + 1 ], $parameterMap, $usedRest );
if ( $hasRest && !$usedRest )
{
throw new ezcTemplateException( sprintf( ezcTemplateSourceToTstErrorMessages::MSG_TOO_MANY_PARAMETERS, $functionName ) );
}
if ( sizeof( $functionDefinition ) == 3 )
{
$ast->typeHint = $functionDefinition[0];
}
else
{
$ast->typeHint = ezcTemplateAstNode::TYPE_ARRAY | ezcTemplateAstNode::TYPE_VALUE;
}
return new ezcTemplateParenthesisAstNode( $ast );
}
/**
* Returns the class that may implement this function $functionName.
*
* @param string $functionName
* @return string
*/
public function getClass( $functionName )
{
foreach ( $this->functionToClass as $func => $class )
{
if ( preg_match( $func, $functionName ) )
{
return $class;
}
}
return null;
}
/**
* Returns a custom function definition if the given function name $name is a custom function,
* otherwise return false.
*
* @param string $name
* @return ezcTemplateCustomFunctionDefinition
*/
public function getCustomFunction( $name )
{
foreach ( $this->parser->template->usedConfiguration->customFunctions as $class )
{
$def = call_user_func( array( $class, "getCustomFunctionDefinition" ), $name );
if ( $def instanceof ezcTemplateCustomFunctionDefinition )
{
return $def;
}
}
return false;
}
/**
* Checks if the definition is correct. It returns nothing if it's correct, and
* throws an exception otherwise.
*
* @param ezcTemplateCustomFunctionDefintion $definition
* @param ReflectionParameters $reflectionParameters
* @throws ezcTemplateException if the given definition is incorrect.
* @return void
*/
protected function checkDefinition( $definition, $reflectionParameters )
{
if ( !isset( $definition->parameters ) || $definition->parameters === false )
{
return;
}
$i = 0;
$foundOptionalParameter = false;
foreach ( $definition->parameters as $p )
{
if ( !isset($reflectionParameters[$i]) )
{
throw new ezcTemplateException( sprintf(ezcTemplateSourceToTstErrorMessages::MSG_INVALID_DEFINITION_PARAMETER_DOES_NOT_EXIST, $i + 1));
}
if ( $p[0] == "[" )
{
if ( !$reflectionParameters[$i]->isOptional() )
{
throw new ezcTemplateException(sprintf(ezcTemplateSourceToTstErrorMessages::MSG_INVALID_DEFINITION_PARAMETER_OPTIONAL, $i + 1));
}
$foundOptionalParameter = true;
}
else
{
if ($foundOptionalParameter)
{
throw new ezcTemplateException(ezcTemplateSourceToTstErrorMessages::MSG_INVALID_DEFINITION_EXPECT_OPTIONAL_PARAMETER);
}
}
$i++;
}
}
/**
* Counts how many parameters are required.
*
* @param ezcTemplateCustomFunctionDefintion $definition
* @param ReflectionParameters $reflectionParameters
* @return int
*/
protected function countRequiredParameters( $definition, $reflectionParameters )
{
$requiredParameters = 0;
foreach ( $reflectionParameters as $p )
{
if ( !$p->isOptional() )
{
$requiredParameters++;
}
}
return $requiredParameters;
}
/**
* Counts the total amount of parameters.
*
* @param ezcTemplateCustomFunctionDefintion $definition
* @param ReflectionParameters $reflectionParameters
* @return int
*/
protected function countTotalParameters($definition, $reflectionParameters)
{
return sizeof( $reflectionParameters );
}
/**
* Checks the given parameters.
*
* @param ezcTemplateCustomFunctionDefintion $definition
* @param ReflectionParameters $reflectionParameters
* @param array(ezcTemplateAstNode) $parameters
* @param string $functionName
* @return int
*/
protected function checkGivenParameters( $definition, $reflectionParameters, $parameters, $functionName )
{
$givenParameters = sizeof($parameters);
if ( $givenParameters < $this->countRequiredParameters($definition, $reflectionParameters) )
{
throw new ezcTemplateException( sprintf( ezcTemplateSourceToTstErrorMessages::MSG_EXPECT_PARAMETER, $functionName, $definition->parameters[$givenParameters]));
}
if ( !$definition->variableArgumentList && $givenParameters > $this->countTotalParameters($definition, $reflectionParameters) )
{
throw new ezcTemplateException( sprintf( ezcTemplateSourceToTstErrorMessages::MSG_TOO_MANY_PARAMETERS, $functionName ) );
}
}
/**
* Orders the parameters.
*
* @param ReflectionParameters $reflectionParameters
* @param array(ezcTemplateAstNode) $parameters
* @param string $functionName
* @return array(ezcTemplateAstNode)
*/
protected function orderParameters($reflectionParameters, $parameters, $functionName)
{
// First assign the named parameters, thereafter fill in the rest.
$newParameters = array();
// Process the named parameters.
$maxNr = 0;
// Named parameters.
foreach ($parameters as $name => $value)
{
if (!is_int($name))
{
$found = false;
$nr = 0;
foreach ( $reflectionParameters as $rp)
{
if ( $rp->getName() === $name )
{
$found = true;
break;
}
$nr++;
}
if ( !$found)
{
throw new ezcTemplateException( sprintf( ezcTemplateSourceToTstErrorMessages::MSG_NAMED_PARAMETER_NOT_FOUND, $name, $functionName ) );
}
$newParameters[$nr] = $value;
$maxNr = max($nr, $maxNr);
}
}
ksort($newParameters, SORT_NUMERIC);
$i = 0;
foreach ($parameters as $name => $value)
{
if (is_int($name))
{
while ( isset($newParameters[$i])) $i++;
$newParameters[$i] = $value;
}
$i++;
}
// Search for 'holes'.
for($i = 0; $i < $maxNr; $i++)
{
if ( !isset( $newParameters[$i]) )
{
if ( !$reflectionParameters[$i]->isDefaultValueAvailable() )
{
throw new ezcTemplateException( sprintf("Parameter %s is not given and has not a default value.", $i + 1) );
}
else
{
$newParameters[$i] = new ezcTemplateLiteralAstNode( $reflectionParameters[$i]->getDefaultValue() );
}
}
}
ksort($newParameters, SORT_NUMERIC);
return $newParameters;
}
/**
* Returns the reflection parameters.
*
* @param ezcTemplateCustomFunctionDefintion $definition
* @return ReflectionParameters
*/
protected function getReflectionParameters( $definition )
{
if ( !isset($definition->class) || $definition->class === false )
{
$rm = new ReflectionFunction( $definition->method );
}
else
{
$rc = new ReflectionClass( $definition->class );
$rm = $rc->getMethod($definition->method);
}
return $rm->getParameters();
}
/**
* Returns the corresponding AST tree to the function name $functionName and its parameters.
*
* The function name will, most likely, be translated from a Template function name to
* the PHP function. The parameters are ordered, if needed.
*
* @param string $functionName
* @param array(ezcTemplateAstNode) $parameters
* @throws ezcTemplateException if the parameters are not valid (Too many, not enough parameters, etc).
* @return ezcTemplateAstNode
*/
public function getAstTree( $functionName, $parameters )
{
// Try the built-in template functions.
$class = $this->getClass( $functionName );
if ( $class !== null )
{
$f = call_user_func( array( $class, "getFunctionSubstitution"), $functionName, $parameters );
if ( $f !== null ) return $this->createAstNodes( $functionName, $f, $parameters );
}
// Try the custom template functions.
$def = $this->getCustomFunction( $functionName );
if ( $def !== false )
{
$reflectionParameters = $this->getReflectionParameters( $def );
$a = new ezcTemplateFunctionCallAstNode( ( $def->class ? ( $def->class . "::" ) : "" ) . $def->method);
$a->checkAndSetTypeHint();
if ( isset($def->sendTemplateObject) && $def->sendTemplateObject )
{
array_shift($reflectionParameters);
$a->appendParameter( new ezcTemplateVariableAstNode("this->template") );
}
$this->checkDefinition($def, $reflectionParameters);
$this->checkGivenParameters($def, $reflectionParameters, $parameters, $functionName);
$parameters = $this->orderParameters( $reflectionParameters, $parameters, $functionName);
foreach ( $parameters as $p)
{
$a->appendParameter( $p );
}
return $a;
}
throw new ezcTemplateException( sprintf( ezcTemplateSourceToTstErrorMessages::MSG_UNKNOWN_FUNCTION, $functionName ) );
}
/**
* Subclasses need to implement this method.
*
* @param string $functionName
* @param array(ezcTemplateAstNode) $parameters
* @throws ezcTemplateInternalException if the subclass does not implement this method.
* @return array(mixed)
*/
public static function getFunctionSubstitution( $functionName, $parameters )
{
throw new ezcTemplateInternalException( "Subclasses need to implement the getFunctionSubstitution method." );
}
}
?>