diff --git a/src/Pluf/Template/Compiler.php b/src/Pluf/Template/Compiler.php
index 9ad2d81..e790e1a 100644
--- a/src/Pluf/Template/Compiler.php
+++ b/src/Pluf/Template/Compiler.php
@@ -48,7 +48,7 @@ class Pluf_Template_Compiler
protected $_vartype = array(T_CHARACTER, T_CONSTANT_ENCAPSED_STRING,
T_DNUMBER, T_ENCAPSED_AND_WHITESPACE,
T_LNUMBER, T_OBJECT_OPERATOR, T_STRING,
- T_WHITESPACE, T_ARRAY, T_CLASS, T_PRIVATE);
+ T_WHITESPACE, T_ARRAY, T_CLASS, T_PRIVATE, T_LIST);
/**
* Assignation operators.
@@ -435,7 +435,7 @@ class Pluf_Template_Compiler
$res = 'elseif('.$this->_parseFinal($args, $this->_allowedInExpr).'):';
break;
case 'foreach':
- $res = 'foreach ('.$this->_parseFinal($args, array_merge(array(T_AS, T_DOUBLE_ARROW, T_STRING, T_OBJECT_OPERATOR, $this->_allowedAssign, '[', ']')), array(';','!')).'): ';
+ $res = 'foreach ('.$this->_parseFinal($args, array_merge(array(T_AS, T_DOUBLE_ARROW, T_STRING, T_OBJECT_OPERATOR, T_LIST, $this->_allowedAssign, '[', ']')), array(';','!')).'): ';
array_push($this->_blockStack, 'foreach');
break;
case 'while':
diff --git a/src/Pluf/Template/Tag/Cycle.php b/src/Pluf/Template/Tag/Cycle.php
new file mode 100644
index 0000000..26c63ba
--- /dev/null
+++ b/src/Pluf/Template/Tag/Cycle.php
@@ -0,0 +1,153 @@
+cycle.
+ *
+ * Cycle among the given strings or variables each time this tag is
+ * encountered.
+ *
+ * Within a loop, cycles among the given strings each time through the loop:
+ *
+ *
+ * {foreach $some_list as $obj}
+ *
+ * ...
+ *
+ * {/foreach}
+ *
+ *
+ * You can use variables, too. For example, if you have two
+ * template variables, $rowvalue1 and $rowvalue2, you can
+ * cycle between their values like this:
+ *
+ *
+ * {foreach $some_list as $obj}
+ *
+ * ...
+ *
+ * {/foreach}
+ *
+ *
+ * You can mix variables and strings:
+ *
+ *
+ * {foreach $some_list as $obj}
+ *
+ * ...
+ *
+ * {/foreach}
+ *
+ *
+ * In some cases you might want to refer to the next value of a cycle
+ * from outside of a loop. To do this, just group the arguments into
+ * an array and give the {cycle} tag name last, like this:
+ *
+ *
+ * {cycle array('row1', 'row2'), 'rowcolors'}
+ *
+ *
+ * From then on, you can insert the current value of the cycle
+ * wherever you'd like in your template:
+ *
+ *
+ * ...
+ * ...
+ *
+ * Based on concepts from the Django cycle template tag.
+ */
+class Pluf_Template_Tag_Cycle extends Pluf_Template_Tag
+{
+ /**
+ * @see Pluf_Template_Tag::start()
+ * @throws InvalidArgumentException If no argument is provided.
+ */
+ public function start()
+ {
+ $nargs = func_num_args();
+ if (1 > $nargs) {
+ throw new InvalidArgumentException(
+ '`cycle` tag requires at least one argument'
+ );
+ }
+
+ $result = '';
+ list($key, $index) = $this->_computeIndex(func_get_args());
+
+ switch ($nargs) {
+ # (array or mixed) argument
+ case 1:
+ $arg = func_get_arg(0);
+ if (is_array($arg)) {
+ $result = $arg[$index % count($arg)];
+ } else {
+ $result = $arg;
+ }
+ break;
+
+ # (array) arguments, (string) assign
+ case 2:
+ $args = func_get_args();
+ if (is_array($args[0])) {
+ $last = array_pop($args);
+ if (is_string($last) && '' === $this->context->get($last)) {
+ $value = Pluf_Utils::flattenArray($args[0]);
+ $this->context->set($last, $value);
+
+ list($assign_key, $assign_index) = $this->_computeIndex(array($value));
+ $result = $value[0];
+ }
+ break;
+ }
+
+ # considers all the arguments as a value to use in the cycle
+ default:
+ $args = Pluf_Utils::flattenArray(func_get_args());
+ $result = $args[$index % count($args)];
+ break;
+ }
+
+ echo Pluf_Template::markSafe((string) $result);
+ }
+
+ /**
+ * Compute an index for the given array.
+ *
+ * @param array
+ * @return array A array of two elements: key and index.
+ */
+ protected function _computeIndex($args)
+ {
+ if (!isset($this->context->__cycle_stack)) {
+ $this->context->__cycle_stack = array();
+ }
+
+ $key = serialize($args);
+ $this->context->__cycle_stack[$key] = (array_key_exists($key, $this->context->__cycle_stack)) ?
+ 1 + $this->context->__cycle_stack[$key] :
+ 0;
+ $index = $this->context->__cycle_stack[$key];
+
+ return array($key, $index);
+ }
+}
diff --git a/src/Pluf/Template/Tag/Firstof.php b/src/Pluf/Template/Tag/Firstof.php
new file mode 100644
index 0000000..40af976
--- /dev/null
+++ b/src/Pluf/Template/Tag/Firstof.php
@@ -0,0 +1,79 @@
+firstof
.
+ *
+ * Outputs the first variable passed that is not false, without escaping.
+ * Outputs nothing if all the passed variables are false.
+ *
+ * Sample usage:
+ *
+ * {firstof array($var1, $var2, $var3)}
+ *
+ * This is equivalent to:
+ *
+ *
+ * {if $var1}
+ * {$var1|safe}
+ * {elseif $var2}
+ * {$var2|safe}
+ * {elseif $var3}
+ * {$var3|safe}
+ * {/if}
+ *
+ *
+ * You can also use a literal string as a fallback value in case all
+ * passed variables are false:
+ *
+ * {firstof array($var1, $var2, $var3), "fallback value"}
+ *
+ * Based on concepts from the Django firstof template tag.
+ */
+class Pluf_Template_Tag_Firstof extends Pluf_Template_Tag
+{
+ /**
+ * @see Pluf_Template_Tag::start()
+ * @param string $token Variables to test.
+ * @param string $fallback Literal string to used when all passed variables are false.
+ * @throws InvalidArgumentException If no argument is provided.
+ */
+ public function start($tokens = array(), $fallback = null)
+ {
+ if (!is_array($tokens) || 0 === count($tokens)) {
+ throw new InvalidArgumentException(
+ '`firstof` tag requires at least one array as argument'
+ );
+ }
+ $result = (string) $fallback;
+
+ foreach ($tokens as $var) {
+ if ($var) {
+ $result = Pluf_Template::markSafe((string) $var);
+ break;
+ }
+ }
+
+ echo $result;
+ }
+}
diff --git a/src/Pluf/Template/Tag/Now.php b/src/Pluf/Template/Tag/Now.php
new file mode 100644
index 0000000..5126b32
--- /dev/null
+++ b/src/Pluf/Template/Tag/Now.php
@@ -0,0 +1,46 @@
+now.
+ *
+ * Displays the date, formatted according to the given string.
+ *
+ * Sample usage:
+ * It is {now "jS F Y H:i"}
+ *
+ * Based on concepts from the Django now template tag.
+ *
+ * @link http://php.net/date for all the possible values.
+ */
+class Pluf_Template_Tag_Now extends Pluf_Template_Tag
+{
+ /**
+ * @see Pluf_Template_Tag::start()
+ * @param string $token Format to be applied.
+ */
+ public function start($token)
+ {
+ echo date($token);
+ }
+}
diff --git a/src/Pluf/Template/Tag/Regroup.php b/src/Pluf/Template/Tag/Regroup.php
new file mode 100644
index 0000000..32cf857
--- /dev/null
+++ b/src/Pluf/Template/Tag/Regroup.php
@@ -0,0 +1,165 @@
+regroup.
+ *
+ * Regroup a list of alike objects by a common attribute.
+ *
+ * This complex tag is best illustrated by use of an example:
+ * say that people is a list of people represented by arrays with
+ * first_name, last_name, and gender keys:
+ *
+ *
+ * $people = array(
+ * array('first_name' => 'George',
+ * 'last_name' => 'Bush',
+ * 'gender' => 'Male'),
+ * array('first_name' => 'Bill',
+ * 'last_name' => 'Clinton',
+ * 'gender' => 'Male'),
+ * array('first_name' => 'Margaret',
+ * 'last_name' => 'Thatcher',
+ * 'gender' => 'Female'),
+ * array('first_name' => 'Condoleezza',
+ * 'last_name' => 'Rice',
+ * 'gender' => 'Female'),
+ * array('first_name' => 'Pat',
+ * 'last_name' => 'Smith',
+ * 'gender' => 'Unknow'),
+ * );
+ *
+ *
+ * ...and you'd like to display a hierarchical list that is ordered by
+ * gender, like this:
+ *
+ *
+ * - Male:
+ *
+ * - George Bush
+ * - Bill Clinton
+ *
+ *
+ * - Female:
+ *
+ * - Margaret Thatcher
+ * - Condoleezza Rice
+ *
+ *
+ * - Unknown:
+ *
+ *
+ *
+ *
+ * You can use the {regroup} tag to group the list of people by
+ * gender. The following snippet of template code would accomplish this:
+ *
+ *
+ * {regroup $people, 'gender', 'gender_list'}
+ *
+ * {foreach $gender_list as $gender}
+ * - {$gender.grouper}:
+ *
+ * {foreach $gender.list as $item}
+ * - {$item.first_name} {$item.last_name}
+ * {/foreach}
+ *
+ *
+ * {/foreach}
+ *
+ *
+ *
+ * Let's walk through this example. {regroup} takes three arguments:
+ * the object (array or instance of Pluf_Model or any object)
+ * you want to regroup, the attribute to group by,and the name of the
+ * resulting object. Here, we're regrouping the people list by the
+ * gender attribute and calling the result gender_list. The result is
+ * assigned in a context varible of the same name $gender_list.
+ *
+ * {regroup} produces a instance of ArrayObject (in this case, $gender_list)
+ * of group objects. Each group object has two attributes:
+ *
+ *
+ * - grouper -- the item that was grouped by
+ * (e.g., the string "Male" or "Female").
+ * - list -- an ArrayObject of all items in this group
+ * (e.g., an ArrayObject of all people with gender='Male').
+ *
+ *
+ * Note that {regroup} does not order its input!
+ *
+ * Based on concepts from the Django regroup template tag.
+ */
+class Pluf_Template_Tag_Regroup extends Pluf_Template_Tag
+{
+ /**
+ * @see Pluf_Template_Tag::start()
+ * @param mixed $data The object to group.
+ * @param string $by The attribute ti group by.
+ * @param string $assign The name of the resulting object.
+ * @throws InvalidArgumentException If no argument is provided.
+ */
+ public function start($data, $by, $assign)
+ {
+ $grouped = array();
+ $tmp = array();
+
+ foreach ($data as $group) {
+ if (is_object($group)) {
+ if (is_subclass_of($group, 'Pluf_Model')) {
+ $raw = $group->getData();
+ if (!array_key_exists($by, $raw)) {
+ continue;
+ }
+ } else {
+ $ref = new ReflectionObject($group);
+ if (!$ref->hasProperty($by)) {
+ continue;
+ }
+ }
+ $key = $group->$by;
+ $list = $group;
+ } else {
+ if (!array_key_exists($by, $group)) {
+ continue;
+ }
+ $key = $group[$by];
+ $list = new ArrayObject($group, ArrayObject::ARRAY_AS_PROPS);
+ }
+
+ if (!array_key_exists($key, $tmp)) {
+ $tmp[$key] = array();
+ }
+ $tmp[$key][] = $list;
+ }
+
+ foreach ($tmp as $key => $list) {
+ $grouped[] = new ArrayObject(array('grouper' => $key,
+ 'list' => $list),
+ ArrayObject::ARRAY_AS_PROPS);
+ }
+ $this->context->set(trim($assign), $grouped);
+ }
+}
diff --git a/src/Pluf/Test/TemplatetagsUnitTestCase.php b/src/Pluf/Test/TemplatetagsUnitTestCase.php
new file mode 100644
index 0000000..9030e15
--- /dev/null
+++ b/src/Pluf/Test/TemplatetagsUnitTestCase.php
@@ -0,0 +1,91 @@
+tag_name.'` template tag.';
+ parent::__construct($label);
+
+ if (null === $this->tag_name) {
+ throw new LogicException('You must initialize the `$tag_name` property.');
+ }
+ if (null === $this->tag_class) {
+ throw new LogicException('You must initialize the `$tag_class` property.');
+ }
+
+ $folder = Pluf::f('tmp_folder').'/templatetags';
+ if (!file_exists($folder)) {
+ mkdir($folder, 0777, true);
+ }
+ $this->tpl_folders = array($folder);
+
+ Pluf_Signal::connect('Pluf_Template_Compiler::construct_template_tags_modifiers',
+ array($this, 'addTemplatetag'));
+ }
+
+ public function addTemplatetag($signal, &$params)
+ {
+ $params['tags'] = array_merge($params['tags'],
+ array($this->tag_name => $this->tag_class));
+ }
+
+ protected function writeTemplateFile($tpl_name, $content)
+ {
+ $file = $this->tpl_folders[0].'/'.$tpl_name;
+ if (file_exists($file)) {
+ unlink($file);
+ }
+ file_put_contents($file, $content);
+ }
+
+ protected function getNewTemplate($content = '')
+ {
+ $tpl_name = sprintf('%s-%s.html',
+ get_class($this),
+ md5($content.microtime(true)));
+ $this->writeTemplateFile($tpl_name, $content);
+
+ return new Pluf_Template($tpl_name, $this->tpl_folders);
+ }
+}
diff --git a/src/Pluf/Tests/TemplateTags/Cycle.php b/src/Pluf/Tests/TemplateTags/Cycle.php
new file mode 100644
index 0000000..f2540df
--- /dev/null
+++ b/src/Pluf/Tests/TemplateTags/Cycle.php
@@ -0,0 +1,166 @@
+skipIf(1, "%s\n " . $message);
+ }
+ }
+
+ public function testNoArguments()
+ {
+ $tpl = $this->getNewTemplate('{cycle}');
+ try {
+ $tpl->render();
+ $this->fail();
+ } catch (InvalidArgumentException $e) {
+ $this->pass();
+ }
+ }
+
+ public function testSimpleCaseInLoop()
+ {
+ $context = new Pluf_Template_Context(array('test' => range(0, 4)));
+ $to_parse = '{foreach $test as $i}'.
+ '{cycle "a", "b"}{$i},'.
+ '{/foreach}';
+ $expected = 'a0,b1,a2,b3,a4,';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testSingleStringArgument()
+ {
+ $context = new Pluf_Template_Context(array('test' => range(0, 4)));
+ $to_parse = '{foreach $test as $i}'.
+ '{cycle "a"}{$i},'.
+ '{/foreach}';
+ $expected = 'a0,a1,a2,a3,a4,';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testSingleArrayArgument()
+ {
+ $context = new Pluf_Template_Context(array('test' => range(0, 4)));
+ $to_parse = '{foreach $test as $i}'.
+ '{cycle array("a", "b", "c")}{$i},'.
+ '{/foreach}';
+ $expected = 'a0,b1,c2,a3,b4,';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testSingleContextVariableArgument()
+ {
+ $context = new Pluf_Template_Context(array('one' => 1));
+ $to_parse = '{cycle $one}{cycle $one}';
+ $expected = '11';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testMultipleCalls()
+ {
+ $to_parse = '{cycle "a", "b"}{cycle "a", "b"}';
+ $expected = 'ab';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render());
+ }
+
+ public function testAssignContextVariable()
+ {
+ $to_parse = '{cycle array("a", "b", "c"), "abc"}'.
+ '{cycle $abc}';
+ $expected = 'ab';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render());
+
+ $to_parse = '{cycle array("a", "b", "c"), "abc"}'.
+ '{cycle $abc}'.
+ '{cycle $abc}';
+ $expected = 'abc';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render());
+
+ $to_parse = '{cycle array("a", "b", "c"), "abc"}'.
+ '{cycle $abc}'.
+ '{cycle $abc}'.
+ '{cycle $abc}';
+ $expected = 'abca';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render());
+ }
+
+ public function testContextVariablesInArrayAsArgument()
+ {
+ $context = new Pluf_Template_Context(array('test' => range(0, 4),
+ 'one' => 1,
+ 'two' => 2));
+ $to_parse = '{foreach $test as $i}'.
+ '{cycle array($one, $two)}'.
+ '{/foreach}';
+ $expected = '12121';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+
+ $context = new Pluf_Template_Context(array('one' => 1,
+ 'two' => 2));
+ $to_parse = '{cycle array($one, $two), "counter"}{cycle $counter}';
+ $expected = '12';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testContextVariablesArgument()
+ {
+ $context = new Pluf_Template_Context(array('test' => range(0, 4),
+ 'first' => 'a',
+ 'second' => 'b'));
+ $to_parse = '{foreach $test as $i}'.
+ '{cycle $first, $second}{$i},'.
+ '{/foreach}';
+ $expected = 'a0,b1,a2,b3,a4,';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testFilterInCycle()
+ {
+ $this->skip('Pluf has no support for applying filters to a variable of array');
+ return;
+
+ $context = new Pluf_Template_Context(array('one' => 'A',
+ 'two' => '2'));
+ $to_parse = '{cycle array($one|lower, $two), "counter"}{cycle $counter}';
+ $expected = 'a2';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+}
diff --git a/src/Pluf/Tests/TemplateTags/Firstof.php b/src/Pluf/Tests/TemplateTags/Firstof.php
new file mode 100644
index 0000000..6201e17
--- /dev/null
+++ b/src/Pluf/Tests/TemplateTags/Firstof.php
@@ -0,0 +1,98 @@
+getNewTemplate('{firstof}');
+ try {
+ $tpl->render();
+ $this->fail();
+ } catch (InvalidArgumentException $e) {
+ $this->pass();
+ }
+ }
+
+ public function testOutputsNothing()
+ {
+ $context = new Pluf_Template_Context(array('a' => 0,
+ 'b' => 0,
+ 'c' => 0));
+ $to_parse = '{firstof array($a, $b, $c)}';
+ $expected = '';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testOutputsMatched()
+ {
+ $to_parse = '{firstof array($a, $b, $c)}';
+
+ $context = new Pluf_Template_Context(array('a' => 1,
+ 'b' => 0,
+ 'c' => 0));
+ $expected = '1';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+
+ $context = new Pluf_Template_Context(array('a' => 0,
+ 'b' => 2,
+ 'c' => 0));
+ $expected = '2';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+
+ $context = new Pluf_Template_Context(array('a' => 0,
+ 'b' => 0,
+ 'c' => 3));
+ $expected = '3';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testOutputsFirstMatch()
+ {
+ $context = new Pluf_Template_Context(array('a' => 1,
+ 'b' => 2,
+ 'c' => 3));
+ $to_parse = '{firstof array($a, $b, $c)}';
+ $expected = '1';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testOutputsFallback()
+ {
+ $context = new Pluf_Template_Context(array('a' => 0,
+ 'b' => 0,
+ 'c' => 0));
+ $to_parse = '{firstof array($a, $b, $c), "my fallback"}';
+ $expected = 'my fallback';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+}
diff --git a/src/Pluf/Tests/TemplateTags/Now.php b/src/Pluf/Tests/TemplateTags/Now.php
new file mode 100644
index 0000000..d990152
--- /dev/null
+++ b/src/Pluf/Tests/TemplateTags/Now.php
@@ -0,0 +1,49 @@
+getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render());
+ }
+
+ public function testParsingEscapedCharaters()
+ {
+ $to_parse = '{now "j \"n\" Y"}';
+ $expected = date("j \"n\" Y");
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render());
+
+ $to_parse = '{now "j \nn\n Y"}';
+ $tpl = $this->getNewTemplate($to_parse);
+ $expected = date("j \nn\n Y");
+ $this->assertEqual($expected, $tpl->render());
+ }
+}
diff --git a/src/Pluf/Tests/TemplateTags/Regroup.php b/src/Pluf/Tests/TemplateTags/Regroup.php
new file mode 100644
index 0000000..78e9614
--- /dev/null
+++ b/src/Pluf/Tests/TemplateTags/Regroup.php
@@ -0,0 +1,208 @@
+_a['verbose'] = 'people';
+ $this->_a['table'] = 'people';
+ $this->_a['model'] = __CLASS__;
+ $this->_a['cols'] = array(
+ 'id' => array(
+ 'type' => 'Pluf_DB_Field_Sequence',
+ 'blank' => true,
+ ),
+ 'first_name' => array(
+ 'type' => 'Pluf_DB_Field_Varchar',
+ 'blank' => true,
+ 'size' => 50,
+ ),
+ 'last_name' => array(
+ 'type' => 'Pluf_DB_Field_Varchar',
+ 'blank' => true,
+ 'size' => 50,
+ ),
+ 'gender' => array(
+ 'type' => 'Pluf_DB_Field_Varchar',
+ 'blank' => true,
+ 'size' => 50,
+ 'default' => 'Unknown',
+ ),
+ );
+ }
+}
+
+class Pluf_Tests_Templatetags_Regroup extends Pluf_Test_TemplatetagsUnitTestCase
+{
+ protected $tag_class = 'Pluf_Template_Tag_Regroup';
+ protected $tag_name = 'regroup';
+
+ public function testRegroupAnArray()
+ {
+ $context = new Pluf_Template_Context(array(
+ 'data' => array(array('foo' => 'c', 'bar' => 1),
+ array('foo' => 'd', 'bar' => 1),
+ array('foo' => 'a', 'bar' => 2),
+ array('foo' => 'b', 'bar' => 2),
+ array('foo' => 'x', 'bar' => 3))));
+ $to_parse = '{regroup $data, "bar", "grouped"}'.
+ '{foreach $grouped as $group}'.
+ '{$group.grouper}:'.
+ '{foreach $group.list as $item}'.
+ '{$item.foo}'.
+ '{/foreach},'.
+ '{/foreach}';
+ $expected = '1:cd,2:ab,3:x,';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testRegroupAnObject()
+ {
+ $obj1 = new stdClass();
+ $obj1->foo = 'c';
+ $obj1->bar = 1;
+ $obj2 = new stdClass();
+ $obj2->foo = 'd';
+ $obj2->bar = 1;
+
+ $obj3 = new stdClass();
+ $obj3->foo = 'a';
+ $obj3->bar = 2;
+ $obj4 = new stdClass();
+ $obj4->foo = 'b';
+ $obj4->bar = 2;
+
+ $obj5 = new stdClass();
+ $obj5->foo = 'x';
+ $obj5->bar = 3;
+
+ $context = new Pluf_Template_Context(array(
+ 'data' => array($obj1, $obj2, $obj3, $obj4, $obj5)));
+ $to_parse = '{regroup $data, "bar", "grouped"}'.
+ '{foreach $grouped as $group}'.
+ '{$group.grouper}:'.
+ '{foreach $group.list as $item}'.
+ '{$item.foo}'.
+ '{/foreach},'.
+ '{/foreach}';
+ $expected = '1:cd,2:ab,3:x,';
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ }
+
+ public function testRegroupPlufModelInstance()
+ {
+ $db = Pluf::db();
+ $schema = new Pluf_DB_Schema($db);
+ $m = new Pluf_Tests_Model_People_Model();
+ $schema->model = $m;
+ $schema->createTables();
+
+ $people = array(
+ array('first_name' => 'George',
+ 'last_name' => 'Bush',
+ 'gender' => 'Male'),
+ array('first_name' => 'Bill',
+ 'last_name' => 'Clinton',
+ 'gender' => 'Male'),
+ array('first_name' => 'Margaret',
+ 'last_name' => 'Thatcher',
+ 'gender' => 'Female'),
+ array('first_name' => 'Condoleezza',
+ 'last_name' => 'Rice',
+ 'gender' => 'Female'),
+ array('first_name' => 'Pat',
+ 'last_name' => 'Smith',
+ 'gender' => 'Unknow'),
+ );
+
+ foreach ($people as $person) {
+ $p = new Pluf_Tests_Model_People_Model();
+ foreach ($person as $key => $value) {
+ $p->$key = $value;
+ }
+ $p->create();
+ }
+ unset($p);
+
+ $people_list = Pluf::factory('Pluf_Tests_Model_People_Model')->getList();
+ $context = new Pluf_Template_Context(array(
+ 'people' => $people_list));
+ $to_parse = <<
+{foreach \$gender_list as \$gender}
+ {\$gender.grouper}:
+
+ {foreach \$gender.list as \$item}
+ - {\$item.first_name} {\$item.last_name}
+ {/foreach}
+
+
+{/foreach}
+
+TPL;
+ $expected = <<
+
+ Male:
+
+
+ - George Bush
+
+ - Bill Clinton
+
+
+
+
+ Female:
+
+
+ - Margaret Thatcher
+
+ - Condoleezza Rice
+
+
+
+
+ Unknow:
+
+
+
+
+HTML;
+ $tpl = $this->getNewTemplate($to_parse);
+ $this->assertEqual($expected, $tpl->render($context));
+ $schema->dropTables();
+ }
+
+}
diff --git a/src/Pluf/Utils.php b/src/Pluf/Utils.php
index bdc4e9c..95d4765 100644
--- a/src/Pluf/Utils.php
+++ b/src/Pluf/Utils.php
@@ -289,4 +289,20 @@ class Pluf_Utils
return base64_decode($data);
}
+ /**
+ * Flatten an array.
+ *
+ * @param array $array The array to flatten.
+ * @return array
+ */
+ public static function flattenArray($array)
+ {
+ $result = array();
+ foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $value) {
+ $result[] = $value;
+ }
+
+ return $result;
+ }
+
}