From 18e49e7ebe531b71f5e3770c43760392cde56fe0 Mon Sep 17 00:00:00 2001 From: franck Date: Tue, 2 Jul 2013 19:18:41 +0200 Subject: [PATCH] Introduction of loop scopes. --- .gitignore | 1 + composer.json | 2 - .../Admin/Controller/BaseAdminController.php | 7 +- .../Core/Template/Element/LoopResultRow.php | 9 ++- .../Core/Template/Smarty/Plugins/Assetic.php | 2 +- .../Template/Smarty/Plugins/TheliaLoop.php | 58 +++++++++++--- .../Template/Smarty/Plugins/Translation.php | 2 +- core/lib/Thelia/Model/Admin.php | 25 ++++++- core/lib/Thelia/Model/Customer.php | 27 ++++++- .../Encoder/PasswordHashEncoderTest.php | 75 +++++++++++++++++++ .../Core/Security/SecurityManagerTest.php | 49 ++++++++++++ .../Token/UsernamePasswordTokenTest.php | 38 ++++++++++ templates/admin/default/login.html | 6 +- templates/smarty-sample/category.html | 43 +++++++++-- templates/smarty-sample/included.html | 12 +++ 15 files changed, 326 insertions(+), 30 deletions(-) create mode 100644 core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php create mode 100644 core/lib/Thelia/Tests/Core/Security/SecurityManagerTest.php create mode 100644 core/lib/Thelia/Tests/Core/Security/Token/UsernamePasswordTokenTest.php create mode 100644 templates/smarty-sample/included.html diff --git a/.gitignore b/.gitignore index b2dc20145..ae4975b08 100755 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ coverage local/cache/* composer.lock web/assets/* +web/.htaccess diff --git a/composer.json b/composer.json index 9cc776517..81055efd8 100755 --- a/composer.json +++ b/composer.json @@ -27,8 +27,6 @@ "symfony/form": "2.2.*", "symfony/validator": "2.2.*", - "symfony/security": "2.2.*", - "symfony/templating": "2.2.*", "smarty/smarty": "v3.1.13", "kriswallsmith/assetic": "1.2.*@dev", diff --git a/core/lib/Thelia/Admin/Controller/BaseAdminController.php b/core/lib/Thelia/Admin/Controller/BaseAdminController.php index 3aa0e9281..f63f87d21 100755 --- a/core/lib/Thelia/Admin/Controller/BaseAdminController.php +++ b/core/lib/Thelia/Admin/Controller/BaseAdminController.php @@ -48,7 +48,7 @@ class BaseAdminController extends ContainerAware */ public function render($templateName, $args = array()) { - $args = array_merge($args, array('lang' => 'fr')); + $args = array_merge($args, array('lang' => 'fr')); // FIXME $response = new Response(); @@ -57,7 +57,7 @@ class BaseAdminController extends ContainerAware public function renderRaw($templateName, $args = array()) { - $args = array_merge($args, array('lang' => 'fr')); + $args = array_merge($args, array('lang' => 'fr')); // FIXME return $this->getParser()->render($templateName, $args); } @@ -90,5 +90,6 @@ class BaseAdminController extends ContainerAware return $this->getFormFactory()->createBuilder("form"); } - + protected function isGranted() { + } } \ No newline at end of file diff --git a/core/lib/Thelia/Core/Template/Element/LoopResultRow.php b/core/lib/Thelia/Core/Template/Element/LoopResultRow.php index 4a14521c7..73d2f937a 100755 --- a/core/lib/Thelia/Core/Template/Element/LoopResultRow.php +++ b/core/lib/Thelia/Core/Template/Element/LoopResultRow.php @@ -29,12 +29,12 @@ class LoopResultRow public function set($key, $value) { - $this->substitution["#".$key] = $value; + $this->substitution[$key] = $value; } public function get($key) { - return $this->substitution["#".$key]; + return $this->substitution[$key]; } public function getVarVal() @@ -42,4 +42,9 @@ class LoopResultRow return $this->substitution; } + public function getVars() + { + return array_keys($this->substitution); + } + } diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/Assetic.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/Assetic.php index f79f98a1a..4418bd50c 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/Assetic.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/Assetic.php @@ -35,7 +35,7 @@ class Assetic implements SmartyPluginInterface { $web_root = THELIA_WEB_DIR; - $asset_dir_from_web_root = '/assets/admin/default'; // FIXME + $asset_dir_from_web_root = 'assets/admin/default'; // FIXME $this->asset_manager = new SmartyAssetsManager($web_root, $asset_dir_from_web_root); } diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php index aad155eae..6c04ef4ba 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/TheliaLoop.php @@ -42,6 +42,9 @@ class TheliaLoop implements SmartyPluginInterface protected $dispatcher; + protected $loopstack = array(); + protected $varstack = array(); + public function __construct(Request $request, EventDispatcherInterface $dispatcher) { $this->request = $request; @@ -71,37 +74,75 @@ class TheliaLoop implements SmartyPluginInterface if ($content === null) { + // Check if a loop with the same name exists in the current scope, and abort if it's the case. + if (array_key_exists($name, $this->varstack)) { + throw new \InvalidArgumentException("A loop named '$name' already exists in the current scope."); + } + $loop = $this->createLoopInstance(strtolower($params['type'])); $this->getLoopArgument($loop, $params); $loopResults = $loop->exec(); - $template->assignByRef($name, $loopResults); + $this->loopstack[$name] = $loopResults; + } else { - $loopResults = $template->getTemplateVars($name); + $loopResults = $this->loopstack[$name]; $loopResults->next(); } if ($loopResults->valid()) { + $loopResultRow = $loopResults->current(); + // On first iteration, save variables that may be overwritten by this loop + if (! isset($this->varstack[$name])) { + + $saved_vars = array(); + + $varlist = $loopResultRow->getVars(); + $varlist[] = 'LOOP_COUNT'; + $varlist[] = 'LOOP_TOTAL'; + + foreach($varlist as $var) { + $saved_vars[$var] = $template->getTemplateVars($var); + } + + $this->varstack[$name] = $saved_vars; + } + foreach($loopResultRow->getVarVal() as $var => $val) { - $template->assign(substr($var, 1), $val); + $template->assign($var, $val); } - $template->assign('__COUNT__', 1 + $loopResults->key()); - $template->assign('__TOTAL__', $loopResults->getCount()); + // Assign meta information + $template->assign('LOOP_COUNT', 1 + $loopResults->key()); + $template->assign('LOOP_TOTAL', $loopResults->getCount()); $repeat = $loopResults->valid(); } + // Loop is terminated. Cleanup. + if (! $repeat) { + + // Restore previous variables values before terminating + if (isset($this->varstack[$name])) { + + foreach($this->varstack[$name] as $var => $value) { + $template->assign($var, $value); + } + + unset($this->varstack[$name]); + } + } if ($content !== null) { if ($loopResults->isEmpty()) $content = ""; + return $content; } } @@ -158,14 +199,11 @@ class TheliaLoop implements SmartyPluginInterface $loopName = $params['rel']; - // Find loop results in the current template vars - $loopResults = $template->getTemplateVars($loopName); - - if (empty($loopResults)) { + if (! isset($this->loopstack[$loopName])) { throw new \InvalidArgumentException("Loop $loopName is not defined."); } - return $loopResults->isEmpty(); + return $this->loopstack[$loopName]->isEmpty(); } /** diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php index 403cfac80..ec984613e 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php @@ -44,7 +44,7 @@ class Translation implements SmartyPluginInterface } // TODO - return "[$string]"; + return "$string"; } /** diff --git a/core/lib/Thelia/Model/Admin.php b/core/lib/Thelia/Model/Admin.php index 854ccb068..68b151193 100755 --- a/core/lib/Thelia/Model/Admin.php +++ b/core/lib/Thelia/Model/Admin.php @@ -3,7 +3,7 @@ namespace Thelia\Model; use Thelia\Model\om\BaseAdmin; - +use Symfony\Component\Security\Core\User\UserInterface; /** * Skeleton subclass for representing a row from the 'admin' table. @@ -16,6 +16,27 @@ use Thelia\Model\om\BaseAdmin; * * @package propel.generator.Thelia.Model */ -class Admin extends BaseAdmin +class Admin extends BaseAdmin implements UserInterface { + /** + * {@inheritDoc} + */ + public function getUsername() { + return $this->getLogin(); + } + + /** + * {@inheritDoc} + */ + public function eraseCredentials() { + $this->setPassword(null); + } + + /** + * {@inheritDoc} + */ + public function getRoles() { + return array(new Role('USER_CUSTOMER')); + } + } diff --git a/core/lib/Thelia/Model/Customer.php b/core/lib/Thelia/Model/Customer.php index 835f91e59..1002d8d94 100755 --- a/core/lib/Thelia/Model/Customer.php +++ b/core/lib/Thelia/Model/Customer.php @@ -3,6 +3,8 @@ namespace Thelia\Model; use Thelia\Model\om\BaseCustomer; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\Role\Role; /** @@ -16,6 +18,29 @@ use Thelia\Model\om\BaseCustomer; * * @package propel.generator.Thelia.Model */ -class Customer extends BaseCustomer +class Customer extends BaseCustomer implements UserInterface { + /** + * {@inheritDoc} + */ + + public function getUsername() { + return $this->getEmail(); + } + + /** + * {@inheritDoc} + */ + public function eraseCredentials() { + $this->setPassword(null); + } + + /** + * {@inheritDoc} + */ + public function getRoles() { + return array(new Role('USER_CUSTOMER')); + } } + + diff --git a/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php b/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php new file mode 100644 index 000000000..7590b3788 --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php @@ -0,0 +1,75 @@ +encode('password', 'sha512', 'a simple salt'); + + // echo "PASS=\{$pass\}"; + + $this->assertEquals("L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ==", $pass, "Expected password not found."); + } + + public function testIsEqual() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $this->assertTrue($encoder->isEqual($exp, 'password', 'sha512', 'a simple salt')); + } + + public function testWrongPass() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $this->assertFalse($encoder->isEqual($exp, 'grongron', 'sha512', 'a simple salt')); + } + + public function testWrongSalt() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $this->assertFalse($encoder->isEqual($exp, 'password', 'sha512', 'another salt')); + } + + public function testWrongAlgo() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $this->assertFalse($encoder->isEqual($exp, 'password', 'md5', 'another salt')); + } + + /** + * @expectedException LogicException + */ + public function testUnsupportedAlgo() + { + $encoder = new PasswordHashEncoder(); + + $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; + + $encoder->isEqual($exp, 'password', 'sbonk', 'another salt'); + } + + /** + * @expectedException LogicException + */ + public function testEncodeWrongAlgorithm() + { + $encoder = new PasswordHashEncoder(); + + $encoder->encode('password', 'pouët', 'a simple salt'); + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Tests/Core/Security/SecurityManagerTest.php b/core/lib/Thelia/Tests/Core/Security/SecurityManagerTest.php new file mode 100644 index 000000000..84cb0d1ef --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Security/SecurityManagerTest.php @@ -0,0 +1,49 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Tests\Security; + +use Thelia\Core\Security\SecurityManager; +/** + * + * @author Franck Allimant + * + */ +class SecurityManagerTest extends \PHPUnit_Framework_TestCase +{ + public function testGetSetToken() + { + /* + $context = new SecurityManager($authProvider)( + $this->getMock('AuthenticationProviderInterface'), + $this->getMock('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface') + ); + $this->assertNull($context->getToken()); + + $context->setToken($token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')); + $this->assertSame($token, $context->getToken()); + */ + // $this->assertFalse(1==1, "faux !"); + } +} diff --git a/core/lib/Thelia/Tests/Core/Security/Token/UsernamePasswordTokenTest.php b/core/lib/Thelia/Tests/Core/Security/Token/UsernamePasswordTokenTest.php new file mode 100644 index 000000000..147c5b816 --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Security/Token/UsernamePasswordTokenTest.php @@ -0,0 +1,38 @@ +assertFalse($token->isAuthenticated()); + + $token = new UsernamePasswordToken('username', 'password', true); + $this->assertTrue($token->isAuthenticated()); + } + + /** + * @expectedException LogicException + */ + public function testSetAuthenticatedToTrue() + { + $token = new UsernamePasswordToken('foo', 'bar', true); + $token->setAuthenticated(true); + } + + public function testSetAuthenticatedToFalse() + { + $token = new UsernamePasswordToken('foo', 'bar', true); + $token->setAuthenticated(false); + $this->assertFalse($token->isAuthenticated()); + } + + public function testEraseCredentials() + { + $token = new UsernamePasswordToken('foo', 'bar', true); + $token->eraseCredentials(); + $this->assertEquals('', $token->getCredentials()); + } +} \ No newline at end of file diff --git a/templates/admin/default/login.html b/templates/admin/default/login.html index 5c33d742e..20fadb19b 100755 --- a/templates/admin/default/login.html +++ b/templates/admin/default/login.html @@ -2,7 +2,7 @@ {include file='includes/header.inc.html'}
- + {{intl l='abcd'}|capitalize} @@ -15,7 +15,7 @@

{intl l='Thelia Back Office'}

-
+ {form_field_hidden form=$form} {form_field form=$form.username} {form_error form=$form.username} @@ -27,7 +27,9 @@ {/form_field} + {form_field form=$form.remember_me} + {/form_field}
diff --git a/templates/smarty-sample/category.html b/templates/smarty-sample/category.html index bc72f9c38..14c4757d6 100644 --- a/templates/smarty-sample/category.html +++ b/templates/smarty-sample/category.html @@ -1,8 +1,39 @@ +{include file="included.html"} + {loop name="category0" type="category" parent="0"} -

1 - CATEGORY : #TITLE

-

-{loop name="category1" type="category" parent="#ID"} -

2 - SUBCATEGORY : #TITLE

+

Out before - CATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {loop name="category1" type="category" parent="#ID"} +

Inner - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} + + {#myid=#ID} + + {loop name="category2" type="category" parent="#ID"} +

Inner 2 before - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ + {loop name="category3" type="category" parent="#myid"} +

Inner inner 2 - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} + +

Inner 2 after - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} + +

Out after - CATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+
+ + {ifloop rel="category2"} +

Hey, y'a d'la categorie 2 !

+ {/ifloop} + + {elseloop rel="category2"} +

Hey, y'a PAS de categorie 2 !

+ {/elseloop} + + {loop name="category2" type="category" parent="#myid"} +

Exter 2 - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} +{/loop} + +{loop name="category2" type="category" parent="1"} +

Final Exter 2 - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

{/loop} -

1bis - CATEGORY : #TITLE

-{/loop} \ No newline at end of file diff --git a/templates/smarty-sample/included.html b/templates/smarty-sample/included.html new file mode 100644 index 000000000..93581ff7f --- /dev/null +++ b/templates/smarty-sample/included.html @@ -0,0 +1,12 @@ +{loop name="included0" type="category" parent="0"} +

Out before - CATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {loop name="category1" type="category" parent="#ID"} +

Inner - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} + + {loop name="category2" type="category" parent="#ID"} +

Inner 2 - SUBCATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+ {/loop} +

Out after - CATEGORY : #TITLE (#LOOP_COUNT / #LOOP_TOTAL)

+
+{/loop} \ No newline at end of file