diff --git a/.env.template b/.env.template
deleted file mode 100644
index e1b749c..0000000
--- a/.env.template
+++ /dev/null
@@ -1 +0,0 @@
-PHP_VERSION=8.3
diff --git a/dev/.env.template b/dev/.env.template
new file mode 100644
index 0000000..c45cb9b
--- /dev/null
+++ b/dev/.env.template
@@ -0,0 +1,6 @@
+PHP_VERSION=8.3
+XDEBUG_PORT=9019
+XDEBUG_MODE=debug,develop
+XDEBUG_IDE_KEY=PHPSTORM
+XDEBUG_IDE_CONFIG=serverName=Docker
+EXTENSIONS_DISABLE=""
diff --git a/dev/init.sh b/dev/init.sh
new file mode 100644
index 0000000..ce7f9bf
--- /dev/null
+++ b/dev/init.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+[ -f ./.env ] || cp ./dev/.env.template ./.env
+echo ".env ok"
diff --git a/dev/php.ini b/dev/php.ini
new file mode 100644
index 0000000..9976163
--- /dev/null
+++ b/dev/php.ini
@@ -0,0 +1,5 @@
+zend_extension=xdebug.so
+xdebug.mode=debug
+xdebug.client_port=9019
+xdebug.idekey=PHPSTORM
+xdebug.client_host=host.docker.internal
diff --git a/docker-compose.yml b/docker-compose.yml
index 0069de9..43c8174 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -2,14 +2,26 @@ version: '3.8'
services:
php:
- build:
- context: .
- args:
- - PHP_VERSION=${PHP_VERSION}
- container_name: php
+ image: wodby/php:${PHP_VERSION}
+ working_dir: /srv/web
+ container_name: templater-php
volumes:
- .:/srv/web
+ - ./dev/php.ini:/usr/local/etc/php/conf.d/z-php-sp-overrides.ini
environment:
- - XDEBUG_MODE=off
+ XDEBUG_CONFIG: client_host=host.docker.internal
+ XDEBUG_MODE: $XDEBUG_MODE
+ PHP_XDEBUG: 1
+ PHP_XDEBUG_DEFAULT_ENABLE: 1
+ PHP_IDE_CONFIG: $XDEBUG_IDE_CONFIG
+ PHP_XDEBUG_CLIENT_PORT: $XDEBUG_PORT
+ PHP_XDEBUG_IDEKEY: $XDEBUG_IDE_KEY
+ PHP_EXTENSIONS_DISABLE: $EXTENSIONS_DISABLE
ports:
- "9000:9000"
+ networks:
+ - itron-network
+
+networks:
+ itron-network:
+ driver: bridge
diff --git a/dockerfile b/dockerfile
deleted file mode 100644
index 8421b68..0000000
--- a/dockerfile
+++ /dev/null
@@ -1,21 +0,0 @@
-ARG PHP_VERSION
-
-FROM php:${PHP_VERSION}-fpm
-
-RUN apt-get update && apt-get install -y \
- git \
- unzip \
- zip \
- && docker-php-ext-install pdo pdo_mysql
-
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-
-WORKDIR /srv/web
-
-COPY . .
-
-RUN chown -R www-data:www-data /srv/web
-
-EXPOSE 9000
-
-CMD ["php-fpm"]
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..fdb87f3
--- /dev/null
+++ b/makefile
@@ -0,0 +1,18 @@
+init:
+ bash ./dev/init.sh
+
+docker.up:
+ docker-compose -p itron-templater up -d
+
+docker.down:
+ docker-compose -p itron-templater down
+
+docker.build.php:
+ docker-compose -p itron-templater up -d --build php
+
+connect.php:
+ docker-compose -p itron-templater exec php bash
+
+tests.php.run:
+ docker-compose -p itron-templater exec php vendor/bin/phpunit
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index c05e186..ab37037 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -9,6 +9,10 @@
>
+
+
+
+
diff --git a/src/Templater.php b/src/Templater.php
index e97a44d..8b64cba 100644
--- a/src/Templater.php
+++ b/src/Templater.php
@@ -137,16 +137,18 @@ private function get_repeaters( $subject ) {
'clear' => [],
];
- foreach ( $m['content'] as $found ):
+ foreach ( $m['content'] as $index => $found ):
$inner = $this->get_repeaters( $found );
if ( is_array( $inner ) ) :
$result['tag'] = array_merge( $result['tag'], $inner['result']['tag'] );
$result['content'] = array_merge( $result['content'], $inner['result']['content'] );
- $result['clear'] = array_merge( $result['clear'],
- [ str_replace( $inner['data'][0], '', $found ) ],
- $inner['result']['clear'] );
+ $result['clear'] = array_merge(
+ $result['clear'],
+ [ $m['tag'][$index] => str_replace( $inner['data'][0], '', $found ) ],
+ $inner['result']['clear'],
+ );
else :
- $result['clear'] [] = $found;
+ $result['clear'][ $m['tag'][$index] ] = $found;
endif;
endforeach;
@@ -167,14 +169,15 @@ private function get_repeaters_data( $data, $context ) {
}
endforeach;
}
- $substr .= ( false !== $key = array_search( $row['tag'], $context['result']['tag'] ) ) ?
- $this->format( $context['result']['clear'][ $key ], $content ) :
- ( is_array( $content ) ? implode( '', $content ) : $content );
+
+ $substr .= $this->format(
+ $context['result']['clear'][ $context['result']['tag'][ array_search( $row['tag'], $context['result']['tag'] ) ] ],
+ $content
+ );
elseif ( ! empty( $content ) ) :
$substr .= ( is_array( $content ) ? implode( '', $content ) : $content );
elseif ( ! empty( $row['tag'] ) ) :
- $substr .= ( false !== $key = array_search( $row['tag'], $context['result']['tag'] ) ) ?
- $context['result']['clear'][ $key ] : '';
+ $substr .= $context['result']['clear'][ $context['result']['tag'][ array_search( $row['tag'], $context['result']['tag'] ) ] ];
endif;
endforeach;
$out = $substr;
diff --git a/tests/TemplaterTest.php b/tests/TemplaterTest.php
index 523109f..24c2716 100644
--- a/tests/TemplaterTest.php
+++ b/tests/TemplaterTest.php
@@ -3,6 +3,8 @@
use iTRON\Templater\Templater;
use PHPUnit\Framework\TestCase;
+require_once 'inc/functions.php';
+
class TemplaterTest extends TestCase {
public function testRender() {
@@ -75,12 +77,14 @@ public function testRender() {
$expected = <<
- CONTENT1
CONTENT1.2
CONTENT1.3
+ CONTENT1
+ CONTENT1.2
+ CONTENT1.3
CONTENT2
EXPECTED;
- $this->assertEquals( $expected, $result );
+ $this->assertEquals( remove_spaces( $expected ), remove_spaces( $result ) );
/**
* Template with a preselected value.
@@ -107,13 +111,15 @@ public function testRender() {
$expected = <<
- CONTENT1
CONTENT1.2
CONTENT1.3
+ CONTENT1
+ CONTENT1.2
+ CONTENT1.3
CONTENT2
value2
EXPECTED;
- $this->assertEquals( $expected, $result );
+ $this->assertEquals( remove_spaces( $expected ), remove_spaces( $result ) );
/**
* The same test with a wrong index.
@@ -132,22 +138,69 @@ public function testRender() {
$expected = <<
- CONTENT1
CONTENT1.2
CONTENT1.3
+ CONTENT1
+ CONTENT1.2
+ CONTENT1.3
CONTENT2
value1
EXPECTED;
- $this->assertEquals( $expected, $result );
+ $this->assertEquals( remove_spaces( $expected ), remove_spaces( $result ) );
+ }
+
+ public function testNestedRepeaters() {
+ $templater = new Templater();
+
+ /**
+ * Template with several tags.
+ */
+ $tpl = <<
+ %s[[list-item]]%s
[[/list-item]]
+
+
+[[logo]]%s
[[/logo]]
+[[text]]%s
[[/text]]
+TEMPLATE;
+
+ $result = $templater->render( $tpl, [
+ [
+ [
+ 'tag' => 'list-item',
+ 'content' => [
+ [
+ [ 'tag' => 'logo', 'content' => 'LOGO1' ],
+ [ 'tag' => 'text', 'content' => 'TEXT1' ],
+ ]
+ ],
+ ],
+ ],
+ ] );
+
+ $expected = <<
+
+
+EXPECTED;
+
+ $this->assertEquals( remove_spaces( $expected ), remove_spaces( $result ) );
/**
* Template with nested repeaters.
*/
$tpl = <<
- %s[[tag1]]
- %s[[tag2]]
%s
[[/tag2]]
-
[[/tag1]]
+ %s
+ [[tag1]]
+
+ %s
+ [[tag2]]
%s
[[/tag2]]
+
+ [[/tag1]]
TEMPLATE;
@@ -173,6 +226,327 @@ public function testRender() {
EXPECTED;
- $this->assertEquals( $expected, $result );
+ $this->assertEquals( remove_spaces( $expected ), remove_spaces( $result ) );
+
+ /**
+ * Tests template with several nested tags.
+ */
+ $tpl = <<
+ %s[[list-item]]
+ %s
+
+ [[logo]]%s
[[/logo]]
+ [[text]]%s
[[/text]]
+ [[/list-item]]
+
+TEMPLATE;
+
+ $result = $templater->render( $tpl, [
+ [
+ [
+ 'tag' => 'list-item',
+ 'content' => [
+ [
+ [ 'tag' => 'logo', 'content' => 'LOGO1' ],
+ [ 'tag' => 'text', 'content' => 'TEXT1' ],
+ ]
+ ],
+ ],
+ ],
+ ] );
+
+ $expected = <<
+
+
+EXPECTED;
+
+
+ $this->assertEquals( remove_spaces( $expected ), remove_spaces( $result ) );
+
+ $tpl = <<
+ %s[[list-item]]
+ %s
+
+ [[logo]]
+
+ %s
+
+ [[/logo]]
+ [[text]]%s
[[/text]]
+ [[img]]
[[/img]]
+ [[/list-item]]
+
+TEMPLATE;
+
+ $data = [
+ [
+ [
+ 'tag' => 'list-item',
+ 'content' => [
+ [
+ [
+ 'tag' => 'logo',
+ 'content' => [
+ [
+ [ 'tag' => 'img', 'content' => 'localhost//image1.jpg' ],
+ ]
+ ]
+ ],
+ [ 'tag' => 'text', 'content' => 'TEXT1' ],
+ ]
+ ],
+ ],
+ ],
+ ];
+
+ $result = $templater->render( $tpl, $data );
+
+ $expected = <<
+
+

+
+ TEXT1
+
+EXPECTED;
+
+
+ $this->assertEquals( remove_spaces( $expected ), remove_spaces( $result ) );
+
+ /**
+ * The same test, but with a deeper nesting.
+ */
+
+ $tpl = <<
+ %s[[list-item]]
+ %s
+
+ [[logo]]
+
+ %s
+ [[img]]

[[/img]]
+
+ [[/logo]]
+ [[text]]%s
[[/text]]
+ [[/list-item]]
+
+TEMPLATE;
+
+ $result = $templater->render( $tpl, $data );
+ // This fails.
+ $this->assertEquals( remove_spaces( $expected ), remove_spaces( $result ) );
+ }
+
+ public function testGetRepeaters() {
+ $templater = new Templater();
+ $method = new ReflectionMethod( Templater::class, 'get_repeaters' );
+ $method->setAccessible( true );
+
+ /**
+ * Simple template with nested repeaters.
+ */
+ $tpl = <<
+ %s[[list-item]]
+ %s[[logo]]%s
[[/logo]][[text]]%s
[[/text]]
+ [[/list-item]]
+
+TEMPLATE;
+
+ $result = $method->invoke( $templater, $tpl );
+
+ $expected = [
+ 'result' => [
+ 'clear' => [
+ 'list-item' => '%s',
+ 'logo' => '%s
',
+ 'text' => '%s
'
+ ],
+
+ 'content' => [
+ '%s[[logo]]%s
[[/logo]][[text]]%s
[[/text]]',
+ '%s
',
+ '%s
'
+ ],
+
+ 'tag' => [
+ 'list-item',
+ 'logo',
+ 'text',
+ ],
+ ],
+ ];
+
+ $this->assertEquals(
+ remove_spaces_recursively( $expected['result'] ),
+ remove_spaces_recursively( $result['result'] )
+ );
+
+ /**
+ * Deep nested.
+ */
+ $tpl = <<
+ %s[[list-item]]
+ %s
+
+ [[logo]]
+
+ %s
+
+ [[/logo]]
+ [[text]]%s
[[/text]]
+ [[img]]
[[/img]]
+ [[/list-item]]
+
+TEMPLATE;
+
+ $result = $method->invoke( $templater, $tpl );
+ $expected = [
+ 'result' => [
+ 'clear' => [
+ 'list-item' => '%s',
+ 'logo' => '%s
',
+ 'text' => '%s
',
+ 'img' => '
',
+ ],
+
+ 'content' => [
+ '%s[[logo]]%s
[[/logo]][[text]]%s
[[/text]][[img]]
[[/img]]',
+ '%s
',
+ '%s
',
+ '
',
+ ],
+
+ 'tag' => [
+ 'list-item',
+ 'logo',
+ 'text',
+ 'img',
+ ],
+ ],
+ ];
+
+ $this->assertEquals(
+ remove_spaces_recursively( $expected['result'] ),
+ remove_spaces_recursively( $result['result'] )
+ );
+
+ /**
+ * The same, but with a deeper nesting.
+ */
+ $tpl = <<
+ %s[[list-item]]
+ %s
+
+ [[logo]]
+
+ %s
+ [[img]]

[[/img]]
+
+ [[/logo]]
+ [[text]]%s
[[/text]]
+ [[/list-item]]
+
+TEMPLATE;
+ $expected = [
+ 'result' => [
+ 'clear' => [
+ 'list-item' => '%s',
+ 'logo' => '%s
',
+ 'text' => '%s
',
+ 'img' => '
',
+ ],
+
+ 'content' => [
+ '%s[[logo]]%s[[img]]

[[/img]]
[[/logo]][[text]]%s
[[/text]]',
+ '%s[[img]]

[[/img]]
',
+ '%s
',
+ '
',
+ ],
+
+ 'tag' => [
+ 'list-item',
+ 'logo',
+ 'text',
+ 'img',
+ ],
+ ],
+ ];
+
+ $result = $method->invoke( $templater, $tpl );
+ // This fails.
+ $this->assertEquals(
+ remove_spaces_recursively( $expected['result'] ),
+ remove_spaces_recursively( $result['result'] )
+ );
+ }
+
+ public function testDeepNested_repeaters_parser() {
+ $templater = new Templater();
+ $method = new ReflectionMethod( Templater::class, 'get_repeaters' );
+ $method->setAccessible( true );
+
+ /**
+ * The same, but with a deeper nesting.
+ */
+ $tpl = <<
+ %s[[list-item]]
+ %s
+
+ [[logo]]
+
+ %s
+ [[img]]

[[/img]]
+ [[caption]]
%s[[/caption]]
+
+ [[/logo]]
+ [[text]]%s
[[/text]]
+ [[/list-item]]
+
+TEMPLATE;
+ $expected = [
+ 'result' => [
+ 'clear' => [
+ 'list-item' => '%s',
+ 'logo' => '%s
',
+ 'img' => '
',
+ 'caption' => '%s',
+ 'text' => '%s
',
+ ],
+
+ 'content' => [
+ '%s[[logo]]%s[[img]]

[[/img]][[caption]]
%s[[/caption]]
[[/logo]][[text]]%s
[[/text]]',
+ '%s[[img]]

[[/img]][[caption]]
%s[[/caption]]
',
+ '%s
',
+ '
',
+ '%s',
+ ],
+
+ 'tag' => [
+ 'list-item',
+ 'logo',
+ 'text',
+ 'img',
+ 'caption',
+ ],
+ ],
+ ];
+
+ $result = $method->invoke( $templater, $tpl );
+ // This fails.
+ $this->assertEquals(
+ remove_spaces_recursively( $expected['result'] ),
+ remove_spaces_recursively( $result['result'] )
+ );
}
}
diff --git a/tests/inc/functions.php b/tests/inc/functions.php
new file mode 100644
index 0000000..8f55c50
--- /dev/null
+++ b/tests/inc/functions.php
@@ -0,0 +1,16 @@
+