Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions src/Compile/AttributeCompiler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace Smarty\Compile;

/**
* This class handles compiling the attributes.
*/
class AttributeCompiler
{
/**
* Array of names of required attributes required by tag
*
* @var array
*/
protected $required_attributes = [];

/**
* Array of names of optional attribute required by tag
* use array('_any') if there is no restriction of attributes names
*
* @var array
*/
protected $optional_attributes = [];

/**
* Shorttag attribute order defined by its names
*
* @var array
*/
protected $shorttag_order = [];

/**
* Array of names of valid option flags
*
* @var array
*/
protected $option_flags = [];

public function __construct(
array $required_attributes = [],
array $optional_attributes = [],
array $shorttag_order = [],
array $option_flags = []
) {
$this->required_attributes = $required_attributes;
$this->optional_attributes = $optional_attributes;
$this->shorttag_order = $shorttag_order;
$this->option_flags = $option_flags;
}

/**
* This function checks if the attributes passed are valid
* The attributes passed for the tag to compile are checked against the list of required and
* optional attributes. Required attributes must be present. Optional attributes are check against
* the corresponding list. The keyword '_any' specifies that any attribute will be accepted
* as valid
*
* @param object $compiler compiler object
* @param array $attributes attributes applied to the tag
*
* @return array of mapped attributes for further processing
*/
public function getAttributes($compiler, $attributes)
{
$_indexed_attr = [];
$options = array_fill_keys($this->option_flags, true);
foreach ($attributes as $key => $mixed) {
// shorthand ?
if (!is_array($mixed)) {
// options flag ?
if (isset($options[trim($mixed, '\'"')])) {
$_indexed_attr[trim($mixed, '\'"')] = true;
// shorthand attribute ?
} elseif (isset($this->shorttag_order[$key])) {
$_indexed_attr[$this->shorttag_order[$key]] = $mixed;
} else {
// too many shorthands
$compiler->trigger_template_error('too many shorthand attributes', null, true);
}
// named attribute
} else {
foreach ($mixed as $k => $v) {
// options flag?
if (isset($options[$k])) {
if (is_bool($v)) {
$_indexed_attr[$k] = $v;
} else {
if (is_string($v)) {
$v = trim($v, '\'" ');
}

// Mapping array for boolean option value
static $optionMap = [1 => true, 0 => false, 'true' => true, 'false' => false];

if (isset($optionMap[$v])) {
$_indexed_attr[$k] = $optionMap[$v];
} else {
$compiler->trigger_template_error(
"illegal value '" . var_export($v, true) .
"' for options flag '{$k}'",
null,
true
);
}
}
// must be named attribute
} else {
$_indexed_attr[$k] = $v;
}
}
}
}
// check if all required attributes present
foreach ($this->required_attributes as $attr) {
if (!isset($_indexed_attr[$attr])) {
$compiler->trigger_template_error("missing '{$attr}' attribute", null, true);
}
}
// check for not allowed attributes
if ($this->optional_attributes !== ['_any']) {
$allowedAttributes = array_fill_keys(
array_merge(
$this->required_attributes,
$this->optional_attributes,
$this->option_flags
),
true
);
foreach ($_indexed_attr as $key => $dummy) {
if (!isset($allowedAttributes[$key]) && $key !== 0) {
$compiler->trigger_template_error("unexpected '{$key}' attribute", null, true);
}
}
}
// default 'false' for all options flags not set
foreach ($this->option_flags as $flag) {
if (!isset($_indexed_attr[$flag])) {
$_indexed_attr[$flag] = false;
}
}

return $_indexed_attr;
}
}
84 changes: 6 additions & 78 deletions src/Compile/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,84 +82,12 @@ protected function formatParamsArray(array $_attr): array {
* @return array of mapped attributes for further processing
*/
protected function getAttributes($compiler, $attributes) {
$_indexed_attr = [];
$options = array_fill_keys($this->option_flags, true);
foreach ($attributes as $key => $mixed) {
// shorthand ?
if (!is_array($mixed)) {
// options flag ?
if (isset($options[trim($mixed, '\'"')])) {
$_indexed_attr[trim($mixed, '\'"')] = true;
// shorthand attribute ?
} elseif (isset($this->shorttag_order[$key])) {
$_indexed_attr[$this->shorttag_order[$key]] = $mixed;
} else {
// too many shorthands
$compiler->trigger_template_error('too many shorthand attributes', null, true);
}
// named attribute
} else {
foreach ($mixed as $k => $v) {
// options flag?
if (isset($options[$k])) {
if (is_bool($v)) {
$_indexed_attr[$k] = $v;
} else {
if (is_string($v)) {
$v = trim($v, '\'" ');
}

// Mapping array for boolean option value
static $optionMap = [1 => true, 0 => false, 'true' => true, 'false' => false];

if (isset($optionMap[$v])) {
$_indexed_attr[$k] = $optionMap[$v];
} else {
$compiler->trigger_template_error(
"illegal value '" . var_export($v, true) .
"' for options flag '{$k}'",
null,
true
);
}
}
// must be named attribute
} else {
$_indexed_attr[$k] = $v;
}
}
}
}
// check if all required attributes present
foreach ($this->required_attributes as $attr) {
if (!isset($_indexed_attr[$attr])) {
$compiler->trigger_template_error("missing '{$attr}' attribute", null, true);
}
}
// check for not allowed attributes
if ($this->optional_attributes !== ['_any']) {
$allowedAttributes = array_fill_keys(
array_merge(
$this->required_attributes,
$this->optional_attributes,
$this->option_flags
),
true
);
foreach ($_indexed_attr as $key => $dummy) {
if (!isset($allowedAttributes[$key]) && $key !== 0) {
$compiler->trigger_template_error("unexpected '{$key}' attribute", null, true);
}
}
}
// default 'false' for all options flags not set
foreach ($this->option_flags as $flag) {
if (!isset($_indexed_attr[$flag])) {
$_indexed_attr[$flag] = false;
}
}

return $_indexed_attr;
return (new AttributeCompiler(
$this->required_attributes,
$this->optional_attributes,
$this->shorttag_order,
$this->option_flags
))->getAttributes($compiler, $attributes);
}

/**
Expand Down
30 changes: 17 additions & 13 deletions src/Compile/FunctionCallCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,18 @@
* Smarty Internal Plugin Compile Registered Function
* Compiles code for the execution of a registered function
*


* @author Uwe Tews
*/

namespace Smarty\Compile;

use Smarty\Compiler\Template;
use Smarty\CompilerException;
use Smarty\FunctionHandler\AttributeFunctionHandlerInterface;

/**
* Smarty Internal Plugin Compile Registered Function Class
*


*/
class FunctionCallCompiler extends Base {

/**
* Attribute definition: Overwrites base class.
*
Expand Down Expand Up @@ -51,16 +45,26 @@ class FunctionCallCompiler extends Base {
*/
public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null): string
{
if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) {

// check and get attributes
$_attr = $this->getAttributes($compiler, $args);
unset($_attr['nocache']);
$attribute_overrides = [];

$_paramsArray = $this->formatParamsArray($_attr);
$_params = 'array(' . implode(',', $_paramsArray) . ')';
if ($functionHandler instanceof AttributeFunctionHandlerInterface) {
$attribute_overrides = $functionHandler->getSupportedAttributes();
}

// check and get attributes
$_attr = (new AttributeCompiler(
$attribute_overrides['required_attributes'] ?? $this->required_attributes,
$attribute_overrides['optional_attributes'] ?? $this->optional_attributes,
$attribute_overrides['shorttag_order'] ?? $this->shorttag_order,
$attribute_overrides['option_flags'] ?? $this->option_flags
))->getAttributes($compiler, $args);

if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) {
unset($_attr['nocache']);

$_paramsArray = $this->formatParamsArray($_attr);
$_params = 'array(' . implode(',', $_paramsArray) . ')';

// not cacheable?
$compiler->tag_nocache = $compiler->tag_nocache || !$functionHandler->isCacheable();
Expand Down
77 changes: 77 additions & 0 deletions src/FunctionHandler/AttributeBase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Smarty\FunctionHandler;

use Smarty\Template;

/**
* Abstract implementation for function handlers which support custom attributes
*/
abstract class AttributeBase implements AttributeFunctionHandlerInterface
{
/**
* Array of names of required attribute required by tag
*
* @var array
*/
protected array $required_attributes = [];

/**
* Array of names of optional attribute required by tag
* use array('_any') if there is no restriction of attributes names
*
* @var array
*/
protected array $optional_attributes = [];

/**
* Shorttag attribute order defined by its names
*
* @var array
*/
protected array $shorttag_order = [];

/**
* Array of names of valid option flags
*
* @var array
*/
protected array $option_flags = [];

/**
* Return whether the output is cacheable.
* @var bool
*/
protected bool $cacheable = true;

/**
* Return whether the output is cacheable.
* @return bool
*/
public function isCacheable(): bool
{
return $this->cacheable;
}

/**
* Function body
* @param mixed $params The supplied parameters.
* @param Smarty\Template $template
* @return mixed
*/
abstract public function handle($params, Template $template): ?string;

/**
* Return the support attributes for this function.
* @return array<string, array>
*/
public function getSupportedAttributes(): array
{
return [
'required_attributes' => $this->required_attributes,
'optional_attributes' => $this->optional_attributes,
'shorttag_order' => $this->shorttag_order,
'option_flags' => $this->option_flags,
];
}
}
15 changes: 15 additions & 0 deletions src/FunctionHandler/AttributeFunctionHandlerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Smarty\FunctionHandler;

/**
* Function handler interface with support for specifying supported properties
*/
interface AttributeFunctionHandlerInterface extends FunctionHandlerInterface
{
/**
* Returns an array with the supported attributes, flags, and shorttags
* @return array<string, array>
*/
public function getSupportedAttributes(): array;
}
Loading