diff --git a/core/lib/Thelia/Core/Template/Element/BaseLoop.php b/core/lib/Thelia/Core/Template/Element/BaseLoop.php index b6085ad7c..519aae4bd 100755 --- a/core/lib/Thelia/Core/Template/Element/BaseLoop.php +++ b/core/lib/Thelia/Core/Template/Element/BaseLoop.php @@ -25,6 +25,7 @@ namespace Thelia\Core\Template\Element; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Thelia\Core\Template\Loop\Argument\ArgumentCollection; /** * @@ -96,7 +97,7 @@ abstract class BaseLoop * ) * ); * - * @return array + * @return ArgumentCollection */ abstract public function defineArgs(); diff --git a/core/lib/Thelia/Core/Template/Loop/Argument/Argument.php b/core/lib/Thelia/Core/Template/Loop/Argument/Argument.php old mode 100644 new mode 100755 index 1be69e18f..d2b6cdbb2 --- a/core/lib/Thelia/Core/Template/Loop/Argument/Argument.php +++ b/core/lib/Thelia/Core/Template/Loop/Argument/Argument.php @@ -29,12 +29,13 @@ namespace Thelia\Core\Template\Loop\Argument; */ class Argument { - protected $name; - protected $type; - protected $mandatory; - protected $default; + public $name; + public $type; + public $default; + public $mandatory; + public $empty; - public function __construct($name, \Thelia\Type\TypeCollection $type, $mandatory = false, $default = null) + public function __construct($name, \Thelia\Type\TypeCollection $type, $default = null, $mandatory = false, $empty = true) { $this->name = $name; $this->type = $type; diff --git a/core/lib/Thelia/Core/Template/Loop/Argument/ArgumentCollection.php b/core/lib/Thelia/Core/Template/Loop/Argument/ArgumentCollection.php old mode 100644 new mode 100755 index 84cf958db..d3422c1e9 --- a/core/lib/Thelia/Core/Template/Loop/Argument/ArgumentCollection.php +++ b/core/lib/Thelia/Core/Template/Loop/Argument/ArgumentCollection.php @@ -65,7 +65,7 @@ class ArgumentCollection implements \Iterator * (PHP 5 >= 5.0.0)
* Return the current element * @link http://php.net/manual/en/iterator.current.php - * @return \Thelia\Core\Template\Element\LoopResultRow + * @return Argument */ public function current() { @@ -80,7 +80,7 @@ class ArgumentCollection implements \Iterator */ public function next() { - ++$this->arguments; + $this->position++; } /** @@ -91,7 +91,7 @@ class ArgumentCollection implements \Iterator */ public function key() { - return $this->arguments; + return $this->position; } /** diff --git a/core/lib/Thelia/Core/Template/Loop/Category.php b/core/lib/Thelia/Core/Template/Loop/Category.php index 5f03336ce..b203c7745 100755 --- a/core/lib/Thelia/Core/Template/Loop/Category.php +++ b/core/lib/Thelia/Core/Template/Loop/Category.php @@ -85,35 +85,33 @@ class Category extends BaseLoop new Argument( 'id', new TypeCollection( - new Type\AnyType() + new Type\IntListType() ) ), new Argument( 'parent', new TypeCollection( - new Type\AnyType() + new Type\IntType() ) ), new Argument( 'current', new TypeCollection( - new Type\AnyType() + new Type\IntType() ) ), new Argument( 'not_empty', new TypeCollection( - new Type\AnyType() + new Type\IntType() ), - false, 0 ), new Argument( 'visible', new TypeCollection( - new Type\AnyType() + new Type\IntType() ), - false, 1 ), new Argument( @@ -125,7 +123,7 @@ class Category extends BaseLoop new Argument( 'order', new TypeCollection( - new Type\AnyType() + new Type\EnumType('alpha', 'alpha_reverse', 'reverse') ) ), new Argument( @@ -133,29 +131,26 @@ class Category extends BaseLoop new TypeCollection( new Type\AnyType() ), - false, 0 ), new Argument( 'exclude', new TypeCollection( - new Type\AnyType() + new Type\IntListType() ) ), new Argument( 'limit', new TypeCollection( - new Type\AnyType() + new Type\IntType() ), - false, 10 ), new Argument( 'offset', new TypeCollection( - new Type\AnyType() + new Type\IntType() ), - false, 0 ) ); diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php index 461b44a86..3258f36c5 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php @@ -23,6 +23,7 @@ namespace Thelia\Core\Template\Smarty\Plugins; +use Thelia\Core\Template\Element\BaseLoop; use Thelia\Core\Template\Smarty\SmartyPluginInterface; use Thelia\Core\Template\Smarty\SmartyPluginDescriptor; @@ -34,6 +35,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; class TheliaLoop implements SmartyPluginInterface { + protected $loopDefinition = array(); protected $request; @@ -61,6 +63,7 @@ class TheliaLoop implements SmartyPluginInterface if (empty($params['name'])) throw new \InvalidArgumentException("Missing 'name' parameter in loop arguments"); + if (empty($params['type'])) throw new \InvalidArgumentException("Missing 'type' parameter in loop arguments"); @@ -84,18 +87,17 @@ class TheliaLoop implements SmartyPluginInterface if ($loopResults->valid()) { - $loopResultRow = $loopResults->current(); - foreach ($loopResultRow->getVarVal() as $var => $val) { + foreach($loopResultRow->getVarVal() as $var => $val) { + $template->assign(substr($var, 1), $val); + } - $template->assign(substr($var, 1), $val); + $template->assign('__COUNT__', 1 + $loopResults->key()); + $template->assign('__TOTAL__', $loopResults->getCount()); - $template->assign('__COUNT__', 1 + $loopResults->key()); - $template->assign('__TOTAL__', $loopResults->getCount()); - } + $repeat = $loopResults->valid(); + } - $repeat = $loopResults->valid(); - } if ($content !== null) { @@ -115,10 +117,11 @@ class TheliaLoop implements SmartyPluginInterface */ public function theliaElseloop($params, $content, $template, &$repeat) { - // When encoutering close tag, check if loop has results. - if ($repeat === false) { - return $this->checkEmptyLoop($params, $template) ? $content : ''; - } + + // When encoutering close tag, check if loop has results. + if ($repeat === false) { + return $this->checkEmptyLoop($params, $template) ? $content : ''; + } } /** @@ -132,10 +135,11 @@ class TheliaLoop implements SmartyPluginInterface */ public function theliaIfLoop($params, $content, $template, &$repeat) { - // When encountering close tag, check if loop has results. - if ($repeat === false) { - return $this->checkEmptyLoop($params, $template) ? '' : $content; - } + + // When encountering close tag, check if loop has results. + if ($repeat === false) { + return $this->checkEmptyLoop($params, $template) ? '' : $content; + } } /** @@ -148,8 +152,9 @@ class TheliaLoop implements SmartyPluginInterface */ protected function checkEmptyLoop($params, $template) { - if (empty($params['rel'])) - throw new \InvalidArgumentException("Missing 'rel' parameter in ifloop/elseloop arguments"); + + if (empty($params['rel'])) + throw new \InvalidArgumentException("Missing 'rel' parameter in ifloop/elseloop arguments"); $loopName = $params['rel']; @@ -199,7 +204,7 @@ class TheliaLoop implements SmartyPluginInterface * @param unknown $smartyParam * @throws \InvalidArgumentException */ - protected function getLoopArgument($loop, $smartyParam) + protected function getLoopArgument(BaseLoop $loop, $smartyParam) { $defaultItemsParams = array('required' => true); @@ -209,33 +214,45 @@ class TheliaLoop implements SmartyPluginInterface $faultActor = array(); $faultDetails = array(); - foreach ($loop->defineArgs() as $name => $param) { - if (is_integer($name)) { - $name = $param; - $param = $defaultItemsParams; + + $argumentsCollection = $loop->defineArgs(); + $argumentsCollection->rewind(); + + while ($argumentsCollection->valid()) { + + $argument = $argumentsCollection->current(); + $argumentsCollection->next(); + + $value = isset($smartyParam[$argument->name]) ? $smartyParam[$argument->name] : null; + + /* check if mandatory */ + if($value === null && $argument->mandatory) { + $faultActor[] = $argument->name; + $faultDetails[] = sprintf('"%s" parameter is missing', $argument->name); + continue; } - if (is_string($param) && array_key_exists($param, $shortcutItemParams)) { - $param = $shortcutItemParams[$param]; + /* check if empty */ + if($value === '' && !$argument->empty) { + $faultActor[] = $argument->name; + $faultDetails[] = sprintf('"%s" parameter cannot be empty', $argument->name); + continue; } - if (!is_array($param)) { - $param = array('default' => $param); + /* check type */ + if($value !== null && !$argument->type->isValid($value)) { + $faultActor[] = $argument->name; + $faultDetails[] = sprintf('Invalid value for "%s" argument', $argument->name); + continue; } - $value = isset($smartyParam[$name]) ? $smartyParam[$name] : null; - - if ($value == null) { - if (isset($param['default'])) { - $value = $param['default']; - } elseif ($param['required'] === true) { - $faultActor[] = $name; - $faultDetails[] = sprintf('"%s" parameter is missing', $name); - continue; - } + /* set default */ + /* did it as last checking for we consider default value is acceptable no matter type or empty restriction */ + if($value === null) { + $value = $argument->default; } - $loop->{$name} = $value; + $loop->{$argument->name} = $value; } if (!empty($faultActor)) { @@ -282,9 +299,10 @@ class TheliaLoop implements SmartyPluginInterface public function getPluginDescriptors() { return array( - new SmartyPluginDescriptor('block', 'loop' , $this, 'theliaLoop'), - new SmartyPluginDescriptor('block', 'elseloop' , $this, 'theliaElseloop'), - new SmartyPluginDescriptor('block', 'ifloop' , $this, 'theliaIfLoop') + + new SmartyPluginDescriptor('block', 'loop' , $this, 'theliaLoop'), + new SmartyPluginDescriptor('block', 'elseloop' , $this, 'theliaElseloop'), + new SmartyPluginDescriptor('block', 'ifloop' , $this, 'theliaIfLoop'), ); } } diff --git a/core/lib/Thelia/Core/Template/Smarty/SmartyParser.php b/core/lib/Thelia/Core/Template/Smarty/SmartyParser.php index 1eb7f5a4d..8fe684de1 100755 --- a/core/lib/Thelia/Core/Template/Smarty/SmartyParser.php +++ b/core/lib/Thelia/Core/Template/Smarty/SmartyParser.php @@ -15,9 +15,11 @@ use Thelia\Core\Template\Exception\ResourceNotFoundException; /** * * @author Franck Allimant + * @author Etienne Roudeix */ class SmartyParser extends Smarty implements ParserInterface { + public $plugins = array(); protected $request, $dispatcher; @@ -57,13 +59,28 @@ class SmartyParser extends Smarty implements ParserInterface // Prevent smarty ErrorException: Notice: Undefined index bla bla bla... $this->error_reporting = E_ALL ^ E_NOTICE; - // Activer le cache, avec une lifetime de 15mn, et en vérifiant que les templates sources n'ont pas été modifiés. - $this->caching = 1; - $this->cache_lifetime = 300; - $this->compile_check = true; + // Si on n'est pas en mode debug, activer le cache, avec une lifetime de 15mn, et en vérifiant que les templates sources n'ont pas été modifiés. + if($debug == false) { + $this->caching = Smarty::CACHING_LIFETIME_CURRENT; + $this->cache_lifetime = 300; + $this->compile_check = true; + } else { + $this->caching = Smarty::CACHING_OFF; + $this->force_compile = true; + } // The default HTTP status $this->status = 200; + + $this->registerFilter('pre', array($this, "pretest")); + } + + public function pretest($tpl_source, \Smarty_Internal_Template $template) + { + $new_source = preg_replace('`{#([a-zA-Z][a-zA-Z0-9\-_]*)(.*)}`', '{\$$1$2}', $tpl_source); + $new_source = preg_replace('`#([a-zA-Z][a-zA-Z0-9\-_]*)`', '{\$$1|default:\'#$1\'}', $new_source); + + return $new_source; } public function setTemplate($template_path_from_template_base) diff --git a/core/lib/Thelia/Tests/Core/Template/Loop/Argument/ArgumentTest.php b/core/lib/Thelia/Tests/Core/Template/Loop/Argument/ArgumentTest.php new file mode 100755 index 000000000..7c5ce5d16 --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Template/Loop/Argument/ArgumentTest.php @@ -0,0 +1,129 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Tests\Core\Template\Loop\Argument; + +use Thelia\Core\Template\Loop\Argument\ArgumentCollection; +use Thelia\Core\Template\Loop\Argument\Argument; +use Thelia\Type; +use Thelia\Type\TypeCollection; + +/** + * + * @author Etienne Roudeix + * + */ +class ArgumentTest extends \PHPUnit_Framework_TestCase +{ + public function testArgumentCollectionConstruction() + { + $collection = new ArgumentCollection( + new Argument( + 'arg0', + new TypeCollection( + new Type\AnyType() + ) + ), + new Argument( + 'arg1', + new TypeCollection( + new Type\AnyType() + ) + ) + ); + + $collection->addArgument( + new Argument( + 'arg2', + new TypeCollection( + new Type\AnyType() + ) + ) + ); + + $this->assertAttributeEquals( + array( + 0 => new Argument( + 'arg0', + new TypeCollection( + new Type\AnyType() + ) + ), + 1 => new Argument( + 'arg1', + new TypeCollection( + new Type\AnyType() + ) + ), + 2 => new Argument( + 'arg2', + new TypeCollection( + new Type\AnyType() + ) + ), + ), + 'arguments', + $collection + ); + } + + public function testArgumentCollectionFetch() + { + $collection = new ArgumentCollection( + new Argument( + 'arg0', + new TypeCollection( + new Type\AnyType() + ) + ), + new Argument( + 'arg1', + new TypeCollection( + new Type\AnyType() + ) + ), + new Argument( + 'arg2', + new TypeCollection( + new Type\AnyType() + ) + ) + ); + + + $arguments = \PHPUnit_Framework_Assert::readAttribute($collection, 'arguments'); + + $collection->rewind(); + while ($collection->valid()) { + + $argument = $collection->current(); + + $this->assertEquals( + $argument, + $arguments[$collection->key()] + ); + + $collection->next(); + } + } +} diff --git a/core/lib/Thelia/Tests/Type/TypeTest.php b/core/lib/Thelia/Tests/Type/TypeTest.php new file mode 100755 index 000000000..4b7c65df8 --- /dev/null +++ b/core/lib/Thelia/Tests/Type/TypeTest.php @@ -0,0 +1,113 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Tests\Type; + +use Thelia\Type; +use Thelia\Type\TypeCollection; + +/** + * + * @author Etienne Roudeix + * + */ +class TypeTest extends \PHPUnit_Framework_TestCase +{ + public function testTypeCollectionConstruction() + { + $collection = new TypeCollection( + new Type\AnyType(), + new Type\AnyType() + ); + + $collection->addType( + new Type\AnyType() + ); + + $this->assertAttributeEquals( + array( + new Type\AnyType(), + new Type\AnyType(), + new Type\AnyType(), + ), + 'types', + $collection + ); + } + + public function testTypeCollectionFetch() + { + $collection = new TypeCollection( + new Type\AnyType(), + new Type\AnyType(), + new Type\AnyType() + ); + + + $types = \PHPUnit_Framework_Assert::readAttribute($collection, 'types'); + + $collection->rewind(); + while ($collection->valid()) { + + $type = $collection->current(); + + $this->assertEquals( + $type, + $types[$collection->key()] + ); + + $collection->next(); + } + } + + public function testTypes() + { + $anyType = new Type\AnyType(); + $this->assertTrue($anyType->isValid(md5(rand(1000, 10000)))); + + $intType = new Type\IntType(); + $this->assertTrue($intType->isValid('1')); + $this->assertTrue($intType->isValid(2)); + $this->assertFalse($intType->isValid('3.3')); + + $floatType = new Type\FloatType(); + $this->assertTrue($floatType->isValid('1.1')); + $this->assertTrue($floatType->isValid(2.2)); + $this->assertFalse($floatType->isValid('foo')); + + $enumType = new Type\EnumType(array("cat", "dog")); + $this->assertTrue($enumType->isValid('cat')); + $this->assertTrue($enumType->isValid('dog')); + $this->assertFalse($enumType->isValid('monkey')); + $this->assertFalse($enumType->isValid('catdog')); + + $intListType = new Type\IntListType(); + $this->assertTrue($intListType->isValid('1')); + $this->assertTrue($intListType->isValid('1,2,3')); + $this->assertFalse($intListType->isValid('1,2,3.3')); + + $jsonType = new Type\JsonType(); + $this->assertTrue($jsonType->isValid('{"k0":"v0","k1":"v1","k2":"v2"}')); + $this->assertFalse($jsonType->isValid('1,2,3')); + } +} diff --git a/core/lib/Thelia/Type/AnyType.php b/core/lib/Thelia/Type/AnyType.php old mode 100644 new mode 100755 diff --git a/core/lib/Thelia/Type/EnumType.php b/core/lib/Thelia/Type/EnumType.php new file mode 100755 index 000000000..d0a1c9b89 --- /dev/null +++ b/core/lib/Thelia/Type/EnumType.php @@ -0,0 +1,50 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Type; + +/** + * + * @author Etienne Roudeix + * + */ + +class EnumType implements TypeInterface +{ + protected $values = array(); + + public function __construct($values = array()) + { + if(is_array($values)) + $this->values = $values; + } + + public function getType() + { + return 'Enum type'; + } + + public function isValid($value) + { + return in_array($value, $this->values); + } +} diff --git a/core/lib/Thelia/Type/FloatType.php b/core/lib/Thelia/Type/FloatType.php new file mode 100755 index 000000000..e341b540f --- /dev/null +++ b/core/lib/Thelia/Type/FloatType.php @@ -0,0 +1,42 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Type; + +/** + * + * @author Etienne Roudeix + * + */ + +class FloatType implements TypeInterface +{ + public function getType() + { + return 'Float type'; + } + + public function isValid($value) + { + return filter_var($value, FILTER_VALIDATE_FLOAT) === false ? false : true; + } +} diff --git a/core/lib/Thelia/Type/IntListType.php b/core/lib/Thelia/Type/IntListType.php new file mode 100755 index 000000000..74b0a6790 --- /dev/null +++ b/core/lib/Thelia/Type/IntListType.php @@ -0,0 +1,47 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Type; + +/** + * + * @author Etienne Roudeix + * + */ + +class IntListType implements TypeInterface +{ + public function getType() + { + return 'Int list type'; + } + + public function isValid($values) + { + foreach(explode(',', $values) as $value) { + if(filter_var($value, FILTER_VALIDATE_INT) === false) + return false; + } + + return true; + } +} diff --git a/core/lib/Thelia/Type/IntType.php b/core/lib/Thelia/Type/IntType.php new file mode 100755 index 000000000..d077a6c89 --- /dev/null +++ b/core/lib/Thelia/Type/IntType.php @@ -0,0 +1,42 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Type; + +/** + * + * @author Etienne Roudeix + * + */ + +class IntType implements TypeInterface +{ + public function getType() + { + return 'Int type'; + } + + public function isValid($value) + { + return filter_var($value, FILTER_VALIDATE_INT) === false ? false : true; + } +} diff --git a/core/lib/Thelia/Type/JsonType.php b/core/lib/Thelia/Type/JsonType.php new file mode 100755 index 000000000..805977a32 --- /dev/null +++ b/core/lib/Thelia/Type/JsonType.php @@ -0,0 +1,43 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Type; + +/** + * + * @author Etienne Roudeix + * + */ + +class JsonType implements TypeInterface +{ + public function getType() + { + return 'Json type'; + } + + public function isValid($value) + { + json_decode($value); + return (json_last_error() == JSON_ERROR_NONE); + } +} diff --git a/core/lib/Thelia/Type/TypeCollection.php b/core/lib/Thelia/Type/TypeCollection.php old mode 100644 new mode 100755 index 6ec10731b..ebbd3274b --- a/core/lib/Thelia/Type/TypeCollection.php +++ b/core/lib/Thelia/Type/TypeCollection.php @@ -65,7 +65,7 @@ class TypeCollection implements \Iterator * (PHP 5 >= 5.0.0)
* Return the current element * @link http://php.net/manual/en/iterator.current.php - * @return \Thelia\Core\Template\Element\LoopResultRow + * @return \Thelia\Type\TypeInterface */ public function current() { @@ -80,7 +80,7 @@ class TypeCollection implements \Iterator */ public function next() { - ++$this->types; + $this->position++; } /** @@ -91,7 +91,7 @@ class TypeCollection implements \Iterator */ public function key() { - return $this->types; + return $this->position; } /** @@ -116,4 +116,25 @@ class TypeCollection implements \Iterator { $this->position = 0; } + + /** + * @param $value + * + * @return bool + */ + public function isValid($value) + { + $this->rewind(); + while ($this->valid()) { + $type = $this->current(); + + if($type->isValid($value)) { + return true; + } + + $this->next(); + } + + return false; + } } diff --git a/core/lib/Thelia/Type/TypeInterface.php b/core/lib/Thelia/Type/TypeInterface.php old mode 100644 new mode 100755 diff --git a/templates/smarty-sample/index.html b/templates/smarty-sample/index.html index 0e3e45efc..be5e00a24 100755 --- a/templates/smarty-sample/index.html +++ b/templates/smarty-sample/index.html @@ -91,4 +91,11 @@ An image from asset directory : {/for} +
+

Loops also work with #

+ {loop type="category" name="catloop1"} + #TITLE : {#DESCRIPTION|upper} #NOTATAG
+ {/loop} +
+ {include file="includes/footer.html"} \ No newline at end of file